Friday, 25 July 2014

char* is not a synonym for char[]

... was the subject of a mail I wrote to friend, who replied:

... no, it's not a synonym. One of the ways in which they differ is in
declarators, as you see.

> int main(int argc, char *argv[])
> {
>         char ok[] = "abcd";
This means 'ok[] is an array, as large as necessary, in writeable
storage, containing the string "abcd\0"'.

>         char *broken = "abcd";

This means 'broken is a pointer to a character, pointing to the literal
string, in unmodified program text, "abcd". (Technically, this should be
'const char *': in C++, this is mandatory.)

Another difference: sizeof(ok) is the size of the array, in bytes;
sizeof(broken) is the size of a pointer to char on this platform.

In *parameter lists*, char *foo and char foo[] are synonyms, because
arrays decay to pointers *when passed to functions*. Otherwise, they are
not. (In this case sizeof() both will give the same answer.)

>         /* this is ok */
>         ok[0]= 'Z';
Yep. That's modifiable storage.

>         printf("ok = %s\n", ok);
>         /* this segfaults */
>         broken[0] = 'Z';
Can't modify program text, this is not FORTRAN where you could pass in
the number 3 to a function and then modify it to change the value of 3
throughout the universe, uh, I mean your program. :P

> TIL C is short for 'Confusion'.
The general rule is that in declarators, you're declaring exactly what
it looks like: foo *bar is a *pointer*, not a buffer of anything, so
assigning to it will assign to *some other buffer somewhere else*. foo[]
bar is a convenience notation for making an array on the stack, as long
as necessary, and then initializing it as with memcpy() with an
appropriate size. (Not strcpy() -- you can do this:

char containsnulls[] = "foo\0bar"

which will make an eight-byte array containing "foo\0bar\0". strcpy()
would only fill in the first four bytes of that.)

This general distinction is crucial to get the hang of, because very
often you want to declare things *not* on the stack -- that outlive
their containing function -- and then you genrally have to declare them
as a foo * and allocate and free them by hand. So remembering where your
things live (that char foo[] lives on the stack, while char foo * merely
puts the pointer on the stack while the data lives elsewhere) is