Your third example:
printf("%s",(char *){'H','i','\0'});
isn't even legal (strictly speaking it's a constraint violation), and you should have gotten at least one warning when compiling it. When I compiled it with gcc with default options, I got 6 warnings:
c.c:3:5: warning: initialization makes pointer from integer without a cast [enabled by default]
c.c:3:5: warning: (near initialization for ‘(anonymous)’) [enabled by default]
c.c:3:5: warning: excess elements in scalar initializer [enabled by default]
c.c:3:5: warning: (near initialization for ‘(anonymous)’) [enabled by default]
c.c:3:5: warning: excess elements in scalar initializer [enabled by default]
c.c:3:5: warning: (near initialization for ‘(anonymous)’) [enabled by default]
The second argument to printf
is a compound literal. It's legal (but odd) to have a compound literal of type char*
, but in this case the initializer-list portion of the compound literal is invalid.
After printing the warnings, what gcc seems to be doing is (a) converting the expression 'H'
, which is of type int
, to char*
, yielding a garbage pointer value, and (b) ignoring the remainder of the initializer elements, 'i'
and '\0'
. The result is a char*
pointer value that points to the (probably virtual) address 0x48
-- assuming an ASCII-based character set.
Ignoring excess initializers is valid (but worthy of a warning), but there is no implicit conversion from int
to char*
(apart from the special case of a null pointer constant, which doesn't apply here). gcc has done its job by issuing a warning, but it could (and IMHO should) have rejected it with a fatal error message. It will do so with the -pedantic-errors
option.
If your compiler warned you about those lines, you should have included those warnings in your question. If it didn't, either crank up the warning level or get a better compiler.
Going into more detail about what happens in each of the three cases:
printf("%s","Hi");
A C string literal like "%s"
or "Hi"
creates an anonymous statically allocated array of char
. (This object is not const
, but attempting to modify it has undefined behavior; this isn't ideal, but there are historical reasons for it.) A terminating '\0'
null character is added to make it a valid string.
An expression of array type, in most contexts (the exceptions are when it's the operand of the unary sizeof
or &
operator, or when it's a string literal in an initializer used to initialize an array object) is implicitly converted to ("decays to") a pointer to the array's first element. So the two arguments passed to printf
are of type char*
; printf
uses those pointers to traverse the respective arrays.
printf("%s",(char[]){'H','i','\0'});
This uses a feature that was added to the language by C99 (the 1999 edition of the ISO C standard), called a compound literal. It's similar to a string literal, in that it creates an anonymous object and refers to the value of that object. A compound literal has the form:
( type-name ) { initializer-list }
and the object has the specified type and is initialized to the value given by the initializer list.
The above is nearly equivalent to:
char anon[] = {'H', 'i', '\0'};
printf("%s", anon);
Again, the second argument to printf
refers to an array object, and it "decays" to a pointer to the array's first element; printf
uses that pointer to traverse the array.
Finally, this:
printf("%s",(char*){'A','B','\0'});
as you say, fails big time. The type of a compound literal is usually an array or structure (or union); it actually hadn't occurred to me that it could be a scalar type such as a pointer. The above is nearly equivalent to:
char *anon = {'A', 'B', '\0'};
printf("%s", anon);
Obviously anon
is of type char*
, which is what printf
expects for a "%s"
format. But what's the initial value?
The standard requires the initializer for a scalar object to be a single expression, optionally enclosed in curly braces. But for some reason, that requirement is under "Semantics", so violating it is not a constraint violation; it's merely undefined behavior. That means the compiler can do anything it likes, and may or may not issue a diagnostic. The authors of gcc apparently decided to issue a warning and ignore all but the first initializer in the list.
After that, it becomes equivalent to:
char *anon = 'A';
printf("%s", anon);
The constant 'A'
is of type int
(for historical reasons, it's int
rather than char
, but the same argument would apply either way). There is no implicit conversion from int
to char*
, and in fact the above initializer is a constraint violation. That means a compiler must issue a diagnostic (gcc does), and may reject the program (gcc doesn't unless you use -pedantic-errors
). Once the diagnostic is issued, the compiler can do whatever it likes; the behavior is undefined (there's some language-lawyerly disagreement on that point, but it doesn't really matter). gcc chooses to convert the value of A
from int
to char*
(probably for historical reasons, going back to when C was even less strongly typed than it is today), resulting in a garbage pointer with a representation that probably looks like 0x00000041
or 0x0000000000000041`.
That garbage pointer is then passed to printf
, which tries to use it to access a string at that location in memory. Hilarity ensues.
There are two important things to keep in mind:
If your compiler prints warnings, pay close attention to them. gcc in particular issues warnings for many things that IMHO should be fatal errors. Never ignore warnings unless you understand what the warning means, thoroughly enough for your knowledge to override that of the authors of the compiler.
Arrays and pointers are very different things. Several rules of the C language seemingly conspire to make it look like they're the same. You can temporarily get away with assuming that arrays are nothing more than pointers in disguise, but that assumption will eventually come back to bite you. Read section 6 of the comp.lang.c FAQ; it explains the relationship between arrays and pointers better than I can.