5

I've been having trouble the past couple hours on a problem I though I understood. Here's my trouble:

void cut_str(char* entry, int offset) {
    strcpy(entry, entry + offset);
}

char  works[128] = "example1\0";
char* doesnt = "example2\0";

printf("output:\n");

cut_str(works, 2);
printf("%s\n", works);

cut_str(doesnt, 2);
printf("%s\n", doesnt);

// output:
// ample1
// Segmentation: fault

I feel like there's something important about char*/char[] that I'm not getting here.

weadmonkey
  • 103
  • 1
  • 4
  • This question is asked here frequently. Please see, e.g., http://stackoverflow.com/questions/10186765/char-array-vs-char-pointer-in-c and http://stackoverflow.com/questions/4090434/strtok-char-array-versus-char-pointer – Jim Balter Jun 29 '12 at 02:17
  • possible duplicate of [What is the difference between char s\[\] and char *s in C?](http://stackoverflow.com/questions/1704407/what-is-the-difference-between-char-s-and-char-s-in-c) – Bo Persson Jun 29 '12 at 10:32

3 Answers3

11

The difference is in that doesnt points to memory that belongs to a string constant, and is therefore not writable.

When you do this

char  works[128] = "example1\0";

the compiler copies the content of a non-writable string into a writable array. \0 is not required, by the way.

When you do this, however,

char* doesnt = "example2\0";

the compiler leaves the pointer pointing to a non-writable memory region. Again, \0 will be inserted by compiler.

If you are using gcc, you can have it warn you about initializing writable char * with string literals. The option is -Wwrite-strings. You will get a warning that looks like this:

 warning: initialization discards qualifiers from pointer target type

The proper way to declare your doesnt pointer is as follows:

const char* doesnt = "example2\0";
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • "the compiler copies the content of a non-writable string into a writable array" -- Not quite. "example1\0" here is not a non-writable string, it is string literal -- a purely syntactic element. In this context it is being used as an initializer. – Jim Balter Jun 29 '12 at 02:05
  • "You should have received a warning about the char *doesnt = ... line" -- No, really, not. No compiler does that, fortunately. – Jim Balter Jun 29 '12 at 02:14
  • 1
    @JimBalter g++ does: "warning: deprecated conversion from string constant to ‘char*’". That's not quite a C compiler, but I wasn't making it up :) – Sergey Kalinichenko Jun 29 '12 at 02:29
  • Right, this is a C question (and answer), not C++. – Jim Balter Jun 29 '12 at 02:44
  • @JimBalter I edited the answer to explain how you get the warning from `gcc`. So much for the "No compiler does that", I guess... – Sergey Kalinichenko Jun 29 '12 at 03:05
  • Thanks for the edit. No compiler does it in a default configuration, as that would lead to many warnings for legacy and common C code. "You should have received ..." is quite different from "You can make the compiler provide ...". And the details are still wrong: the compiler doesn't copy anything to `works`, although it might generate code that does so if `works` is on the stack -- but whether what it copies is writable is irrelevant since it isn't accessible. And `doesnt` may or may not point to non-writable memory -- some systems don't even have such a thing. – Jim Balter Jun 29 '12 at 03:56
4

The types char[] and char * are quite similar, so you are right about that. The difference lies in what happens when objects of the types are initialized. Your object works, of type char[], has 128 bytes of variable storage allocated for it on the stack. Your object doesnt, of type char *, has no storage on the stack.

Where exactly the string of doesnt is stored is not specified by the C standard, but most likely it is stored in a nonmodifiable data segment loaded when your program is loaded for execution. This isn't variable storage. Thus the segfault when you try to vary it.

thb
  • 13,796
  • 3
  • 40
  • 68
  • "The difference lies" -- There are other, significant, differences between arrays and pointers. The issue here really isn't about that, but about the fact that doesnt points to a string constant, and the standard says that the results of modifying it are undefined. – Jim Balter Jun 29 '12 at 02:15
3

This allocates 128 bytes on the stack, and uses the name works to refer to its address:

char works[128];

So works is a pointer to writable memory.

This creates a string literal, which is in read-only memory, and uses the name doesnt to refer to its address:

char * doesnt = "example2\0";

You can write data to works, because it points to writable memory. You can't write data to doesnt, because it points to read-only memory.

Also, note that you don't have to end your string literals with "\0", since all string literals implicitly add a zero byte to the end of the string.

Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
  • No, `works` is an array object, not a pointer. (Its name decays to a pointer expression in most contexts.) – Keith Thompson Jun 29 '12 at 01:25
  • "This allocates 128 bytes on the stack" -- we don't know the storage class; it could be extern. And as Keith notes, arrays are not pointers. (One consequence is that `sizeof(works) != sizeof((char*)works)`). – Jim Balter Jun 29 '12 at 02:08