3

I was wondering what the differences are between the following definitions:

// file.cpp:
namespace n
{
static char const * const str1 = "hello";
static char const str2[] = "hello";
}

Behaviors I want, and I think they both provide:

  • They both refer to immutable data (because the data is char consts)
  • Neither variable can be modified (because str1 is defined as a * const and because str2 is an array, which can't be used as an l-value?)
  • They both have internal linkage (via static)
  • They both have namespace scope
  • If a pointer to either string data is made available to a different module (via some function not specified here), the memory for those strings will be valid (str1 because it points to a string literal, and str2 because the array is declared at namespace scope)

Is there any differences that are guaranteed by the language? If there are behaviors that are implementation dependent, how can I investigate the difference on my different platforms?

(For this example, I am not interested in contrasting these behaviors with std::string options, although feel free to talk about that too if you think other readers would be interested.)

3 Answers3

5

Yes, there are: one is still an array, the other one is a pointer. And pointers and arrays are not the same.

One specific aspect may be the use of the sizeof operator - for the pointer, it won't result in the length of the string, on the array it will (the terminating NUL character counted in as well, of course).

4

You can easily run into the differences between the two stemming from the fact that one is an array and another is a pointer. For example sizeof will evaluate to different (i.e. unrelated) values for n::str1 and n::str2. Also, both of them are lvalues (what made you think they are not?), which means that you can apply & to them and get completely different results

&n::str1; // evaluates to a `char const *const *` value
&n::str2; // evaluates to a `char const (*)[6]` value

Also note that the first one is a direct pointer into some immutable implementation-owned "string literal storage area", while the second one is an array owned by "you", which is initialized by copying data from the aforementioned "string literal storage area". The implementation is allowed to merge those implementation-owned string literals across the entire program. For example, of you declare another pointer initialized with the same string literal

char const *str_another = "hello";

the language guarantees that str_another does not point at n::str2

assert(str_another != n::str2); // will not fail

Yet it does not guarantee that str_another is different from n::str1

assert(str_another != n::str1); // can fail
AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • The l-value came up in VC++2012 'n::str2 = n::str2;' yielded "error C2106: '=' : left operand must be l-value". The primary thing I was trying to emphasize is that neither str1 or str2 was assignable. The reason why is also interesting, but perhaps secondary. – user2246255 Jun 11 '13 at 19:06
  • 1
    @user2246255: `n::str2 = n::str2` is a bad example, since it can fail simply because of array type decay. But anyway, assignment requires a *modifiable* lvalue on the LHS. If something is non-assignable it does not mean that it is not lvalue. It might mean that the lvalue is simply non-modifiable. The `n::str2` is a non-modifiable lvalue. – AnT stands with Russia Jun 11 '13 at 19:08
1

&n::str1 is a char const * const *, but &n::str2 is a char const (*)[6].

You can get some differences with function overload resolution, but probably only if one of the overloads uses a reference-to-array type.

template<typename T>
void f(T); // #1

template<typename T, std::size_t N>
void f(T (&)[N]); // #2

void g() {
    f(n::str1); // calls #1, T is char const*
    f(n::str2); // calls #2, T is char const, N is 6.
}

Also, as @H2CO3 points out, sizeof(n::str1) is sizeof(char const*), but sizeof(n::str2) is 6.

aschepler
  • 70,891
  • 9
  • 107
  • 161