When you allocate an array in C, what you get is something like the following:
+---+
arr[0]: | |
+---+
arr[1]: | |
+---+
...
+---+
arr[N-1]: | |
+---+
That's it. There's no separate memory location set aside for an object named arr
to store the address of the first element of the array. Thus, the address of the first element of the array (&arr[0]
) is the same value as the address of the array itself (&arr
).
Except when it is the operand of the sizeof
or unary &
operators, or is a string literal being used to initialize another array in a declaration, an expression of type "N-element array of T
" will be converted ("decay") to an expression of type "pointer to T
" and the value of the expression will be the address of the first element of the array.
So the type of the expression arr
in the first printf
call is char [10]
; by the rule above, the expression "decays" to type char *
, and the value is the address of arr[0]
.
In the expression &arr
, arr
is the operand of the unary &
operator, so the conversion isn't applied; instead of getting an expression of type char **
, you get an expression of type char (*)[10]
(pointer to 10-element array of char
). Again, since the address of the first element of the array is the same as the address of whole array, the expressions arr
and &arr
have the same value.