char []
denotes the type "array of unknown bound of char", while char *
denotes the type "pointer to char". As you've observed, when a definition of a variable of type "array of unknown bound of char" is initialised with a string literal, the type is converted to "array[N] of char" where N is the appropriate size. The same applies in general to initialisation from array aggregate:
int arr[] = { 0, 1, 2 };
arr is converted to type "array[3] of int".
In a user-defined type definition (struct
, class
or union
), array-of-unknown-bound types are prohibited in C++, although in some versions of C they are allowed as the last member of a struct, where they can be used to access allocated memory past the end of the struct; this usage is called "flexible arrays".
Recursive type construction is another difference; one can construct pointers to and arrays of char *
(e.g. char **
, char (*)[10]
) but this is illegal for arrays of unknown bound; one cannot write char []*
or char [][10]
(although char (*)[]
and char [10][]
are fine).
Finally, cv-qualification operates differently; given typedef char *ptr_to_char
and typedef char array_of_unknown_bound_of_char[]
, cv-qualifiying the pointer version will behave as expected, while cv-qualifying the array version will migrate the cv-qualification to the element type: that is, const array_of_unknown_bound_of_char
is equivalent to const char []
and not the fictional char (const) []
. This means that in a function definition, where array-to-pointer decay operates on the arguments prior to constructing the prototype,
void foo (int const a[]) {
a = 0;
}
is legal; there is no way to make the array-of-unknown-bound parameter non-modifiable.