1

I'm making a bit of C code compile as C++ and have come across something that's puzzling me. Consider the following function taken from LuaFileSystem.

static const char *perm2string (unsigned short mode) {
  static char perms[10] = "---------";
  //static char* perms = "---------";
  int i;
  for (i=0;i<9;i++) perms[i]='-';
  if (mode  & _S_IREAD)
   { perms[0] = 'r'; perms[3] = 'r'; perms[6] = 'r'; }
  if (mode  & _S_IWRITE)
   { perms[1] = 'w'; perms[4] = 'w'; perms[7] = 'w'; }
  if (mode  & _S_IEXEC)
   { perms[2] = 'x'; perms[5] = 'x'; perms[8] = 'x'; }
  return perms;
}

This code will work correctly, however if I uncomment the commented line, it crashes. I've stepped over this with a debugger and it seems that with static char* perms the string is placed in read-only memory and so the first loop will cause an access violation, using the static array causes no such issues. I'm curious as to why this is happening when the string isn't declared const.

user1520427
  • 1,345
  • 1
  • 15
  • 27
  • `"---------"` is a _string literal_. It's a _static array of 10 **const** chars_ (the 9 '-' plus the "null terminator"), it's _immutable_. You can get a pointer to its first char with `const char* p = "---------";` but if you want to modify it you must copy it into a separate array (`perms` in your example). – gx_ Jul 02 '13 at 12:18
  • > This code will work correctly... until you use it concurrently. When multiple threads try to access that static character string/array, you will get strange behaviors. Since your code has only 8 different conditions, you could very well use a switch/case lookup and return a predefined string literal for each: `switch (mode) { case 0 : return "---------"; case _S_IREAD : return "r--r--r--"; /* etc... */ }` That way the function is read only, threadsafe, and probably faster than the char array fiddling you do now. – Arne Mertz Jul 02 '13 at 12:35

6 Answers6

3

That's right.

String literals are immutable, and "---------" is a string literal. Your static char* points to that string literal.

The immutability is not inherently enforced at compile-time, so when you try to write to the literal, you get undefined behaviour instead. This may result in a runtime crash.

[C++11: 2.14.5/12]: Whether all string literals are distinct (that is, are stored in nonoverlapping objects) is implementation-defined. The effect of attempting to modify a string literal is undefined.

C++ actually requires that this pointer be a static char const*, though some compilers only warn about that.

However, initialising an array with a string literal will copy the string. The array is your own, to do with as you please. That's why you can modify the static char[10] without a crash.

[C++11: 8.5.2/1]: A char array (whether plain char, signed char, or unsigned char), char16_t array, char32_t array, or wchar_t array can be initialized by a narrow character literal, char16_t string literal, char32_t string literal, or wide string literal, respectively, or by an appropriately-typed string literal enclosed in braces. Successive characters of the value of the string literal initialize the elements of the array.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • Thanks for the spec quotes, I guess I was relying on the compiler to warn me about this (MSVC2010 doesn't even give me a warning with -Wall set) – user1520427 Jul 02 '13 at 12:22
  • 1
    @user1520427: `-Wall` is more like `-Wsome`. Use `-Wextra` and `-pedantic`, too (for GCC; not sure about VS) – Lightness Races in Orbit Jul 02 '13 at 12:24
  • 5
    @Shahbaz: What's "dirty" about using the Stack Overflow _edit_ function to improve my answer and add value for the OP? I _wasn't_ the first answer; in fact, I was only six seconds ahead of yours. Please check your facts before throwing around vicious accusations. And are you suggesting that, at 82k, I should be forced to stop playing? – Lightness Races in Orbit Jul 02 '13 at 12:47
  • 2
    You played dirty and got two upvotes? You are a terrible dirty player. – R. Martinho Fernandes Jul 02 '13 at 12:48
  • @R.MartinhoFernandes: Does my evil know no bounds? – Lightness Races in Orbit Jul 02 '13 at 12:49
  • 2
    It's dirty because he's losing!!!! –  Jul 02 '13 at 12:49
  • @LightnessRacesinOrbit, I've been around to know how SO works. Simple questions like this get a large view in very small time. Small but fast one line answers often get a few up votes before they know it. Then good answers appear and get higher votes in the end. So intentionally, one can write a quick answer, absorb the initial upvotes, then incrementally improve the answer very quickly so it wouldn't stay behind the competitors. – Shahbaz Jul 02 '13 at 12:53
  • I call that dirty a trick. Because right from the beginning, you know that you're going to make references to the standard and make a complete explanation. Why not write the whole thing and then click answer? Because you know that by this "dirty trick" you eventually get higher votes. And no, you're not evil, but I just don't understand why you are going all that way for more rep when you already have so much. @ScottW, like I care. I rarely even answer these simple questions. – Shahbaz Jul 02 '13 at 12:53
  • 1
    Guys you're arguing about _points_ on a duplicate question. Move on? (or `goto` Meta) – sehe Jul 02 '13 at 12:56
  • @Shahbaz: You have the same number of upvotes that I do. I got no significant rep from this answer, and I didn't post it to get rep. What's the problem? Move on. – Lightness Races in Orbit Jul 02 '13 at 13:01
3

When you write the following:

const char *X = "...."

You have "...." in read-only memory and X pointing to it. In fact, type of X should be const char *, not just char *.

On the other hand, when you write:

char X[] = "...."

This is equivalent to:

char X[] = {'.', '.', '.', '.', '\0'}

which is an array initializer. In other words, X would be an array (not a pointer) and it would contain the contents of "....". Since it's not const, you can change it without problem.

Shahbaz
  • 46,337
  • 19
  • 116
  • 182
2

String literals are const char *. You are not allowed to modify them in C++ or C.

For legacy reasons they can be converted to char *, however this does not make it legal to modify them.

bames53
  • 86,085
  • 15
  • 179
  • 244
1

String literals are stored in read only section of memory. Any attempt to modify the contents of a string literal invokes Undefined Behaviour and segmentation fault on most implementations. SO if you need to have a modifiable char array then declare it as char perms[10] instead of char* perms

0

It's normal and nothing to do with c++ (you will have the same results with C). static char* perms = "---------"; is const because "---------" is a part of your binary (you can see it with objdump programm)

nouney
  • 4,363
  • 19
  • 31
0

char* perms = "-----" creates a pointer pointing to the string, which is located in a protected segment. Trying to write to it will cause a crash.

char perms[] = "-----" creates a character array on the stack and populates it with the relevant characters from "-----".

This is an example of arrays and pointers being inequivalent.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
xen-0
  • 709
  • 4
  • 12