char str[] = "xyz";
declares str as a 4-element array of char and copies the contents of the string initializer into it:
+---+---+---+---+
str: |'x'|'y'|'z'| 0 |
+---+---+---+---+
In this case, the size of the array is taken from the size of the initializer (3 characters plus string terminator).
char str[4] = "xyz";
does the exact same thing, except the size is explicitly specified.
In both cases, the contents of str are writable - you can modify the characters of the string, like so:
str[0] = 'X';
str[1] = 'Y';
...
The maximum length of the string is fixed, however - you can only store strings up to 3 characters long (plus the 0 terminator) in str.
If you had declared it
char *str = "xyz";
then str would be a pointer to the first element of the string literal, like so:
+---+ +---+---+---+---+
str:| | ---> |'x'|'y'|'z'| 0 |
+---+ +---+---+---+---+
All str stores is the address of the first character of the string. In this case, attempting to modify any of the characters in the string leads to undefined behavior. String literals are supposed to be immutable, but they are not necessarily stored in read-only memory. Code like
str[0] = 'X';
may work as expected, or it may cause a runtime error, or it may do nothing at all.
To be safe, you should declare pointers to string literals as const char *:
const char *str = "xyz";
That way if you write something like
str[i] = 'X';
the compiler will yell at you for it, so you don't have to wait until runtime to discover the error.
\0).char *str = "xyz";and will fall under your first description.