The expression "str"
is of type char[4]
. (In C++, it would be const char[4]
.)
Any expression of array type is, in most contexts, implicitly converted to a pointer to the first element of the array object. This conversion is commonly referred to as "decaying". The exceptions to this are:
- When it's the operand of the unary
sizeof
operator (sizeof "str"
yields the size of the array, not the size of a pointer).
- When it's the operand of the unary
&
operator (&"str"
yields a result of type char(*)[4]
, not char*
).
- When it's a string literal in an initializer used to initialize an array object (not applicable here).
In these three cases, an array expression keeps its array type.
A string literal refers to an implicitly created array object with static storage duration, big enough to hold the characters of the literal plus the terminating '\0'
null character. In this case, "str"
refers to an anonymous static object of type char[4]
.
So:
printf("%p\n", "str");
"str"
is implicitly converted to a char*
value, pointing to the 's'
of "str"
.
printf("%p\n", &"str"[0]);
The "str"
in "str"[0]
decays to char*
, as above. "str"[0"
yields a char*
value, pointing to the 's'
. So "str"
and "str"[0]
are of the same type and value.
printf("%p\n", &"str");
Here, since "str"
is the operand of &
, the decay doesn't occur, so &"str"
yields the address of the anonymous char[4]
object, not the address of its first character. This expression is of type char(*)[4]
, or "pointer to array of 4 char".
The expressions &"str"[0]
and &"str"
both yield pointer values, both of which point to the same location in memory, but they're of different types.
In all three cases, the result of evaluating the expression is passed as an argument to printf
. printf
with a "%p"
format requires an argument of type void*
. In the first two cases, you're passing a char*
, and the language's requirements for char*
and void*
imply that it will work as expected. In the third case, there are no such rules for char*
vs. char(*)[4]
, so the behavior of
printf("%p\n", &"str");
is undefined.
As it happens, in most implementations all pointer types have the same size and representation, so you can get away with passing a pointer of any arbitrary type to printf
with "%p"
.
In all three cases, you could (and probably should) explicitly cast the expression to void*
, avoiding the undefined behavior:
printf("%p\n", (void*)"str");
printf("%p\n", (void*)&"str"[0]);
printf("%p\n", (void*)&"str");
The second part of your question deals with a distinct issue; it's about pointers to functions. The rules for expressions of pointer type are similar to those of array type: an expression of function type (such as a function name) is implicitly converted to a pointer to the function, except when it's the operand of sizeof
(which is illegal) or &
(which yields the address of the function). That's why applying *
or &
to a function acts like a no-op. In *func
, func
first decays to a pointer to the function, *
dereferences the pointer, and the result again decays to a pointer to the function. In &func
, the &
inhibits the decay, but it yields the same pointer to the function that func
by itself would yield.