Your third example:
printf("%s",(char *){'H','i','\0'});
itโs not even legal (strictly speaking, this is a violation of the restriction), and you should get at least one warning when compiling it. When I compiled it using gcc with default settings, I received 6 warnings:
cc:3:5: warning: initialization makes pointer from integer without a cast [enabled by default] cc:3:5: warning: (near initialization for '(anonymous)') [enabled by default] cc:3:5: warning: excess elements in scalar initializer [enabled by default] cc:3:5: warning: (near initialization for '(anonymous)') [enabled by default] cc:3:5: warning: excess elements in scalar initializer [enabled by default] cc:3:5: warning: (near initialization for '(anonymous)') [enabled by default]
The second argument to printf is a composite literal. It has the right (but odd) to have a composite literal of type char* , but in this case, part of the list of initializers of the composite 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* giving the value of the garbage pointer and (b) ignoring the rest of the initialization elements, 'i' and '\0' . The result is a char* pointer value pointing to a (possibly virtual) address of 0x48 - subject to an ASCII-based character set.
Ignoring redundant initializers is acceptable (but noteworthy), but there is no implicit conversion from int to char* (except in the special case of the null pointer constant, which is not applicable here). gcc did its job by issuing a warning, but it could (and IMHO) reject it with a fatal error message. He will do this with the -pedantic-errors option.
If your compiler warned you about these lines, you should have included these warnings in your question. If he did not, roll up the warning level or get a better compiler.
In more detail about what happens in each of the three cases:
printf("%s","Hi");
The string literal C as "%s" or "Hi" creates an anonymous, statically allocated char array. (This object is not const , but trying to modify it has undefined behavior, this is not ideal, but there are historical reasons for this.) A trailing '\0' null character is added to make it a valid string.
An array type expression in most contexts (exceptions are when the operand of the unary sizeof or & operator or when it is a string literal in the initializer used to initialize the array object) is implicitly converted to a ("decays to") pointer to the first element of the array. Thus, the two arguments passed to printf are of type char* ; printf uses these pointers to move the corresponding arrays.
printf("%s",(char[]){'H','i','\0'});
It uses a function added to the C99 language (1999 edition of the ISO C standard), called a composite literal. It is similar to a string literal because it creates an anonymous object and refers to the value of that object. The combined literal has the form:
( type-name ) { initializer-list }
and the object has the specified type and is initialized with the value specified in the list of initializers.
The above is almost equivalent:
char anon[] = {'H', 'i', '\0'}; printf("%s", anon);
Again, the second argument to printf refers to the array object and splits into a pointer to the first element of the array; printf uses this pointer to move the array.
Finally, this:
printf("%s",(char*){'A','B','\0'});
as you say, fails. The type of a composite literal is usually an array or structure (or union); it didnโt actually occur to me that it could be a scalar type, such as a pointer. The above is almost equivalent:
char *anon = {'A', 'B', '\0'}; printf("%s", anon);
Obviously, anon is of type char* , which means printf for the format "%s" . But what is the initial value?
The standard requires that the initializer for a scalar object be the only expression, optionally enclosed in curly braces. But for some reason this requirement is under "semantics", therefore, its violation is not a violation of the restriction; this is just undefined behavior. This means that the compiler can do whatever it likes and may or may not issue diagnostics. The gcc authors apparently decided to issue a warning and ignore all but the first initializer in the list.
After that, it becomes equivalent:
char *anon = 'A'; printf("%s", anon);
The constant 'A' is of type int (for historical reasons it is int , not char , but the same argument will apply anyway). There is no implicit conversion from int to char* , and in fact the aforementioned initializer is a violation of the constraint. This means that the compiler must run the diagnostic (gcc does) and may reject the program (gcc does not work if you do not use -pedantic-errors ). Once the diagnostics are issued, the compiler can do whatever it likes; undefined behavior (there is some legal disagreement on this issue, but in fact it does not matter). gcc decides to convert the value of A from int to char* (probably for historical reasons, returning to when C was even less strongly typed than today), resulting in a pointer to the garbage with a view that probably looks like 0x00000041 or 0x0000000000000041`.
The garbage pointer is then passed to printf , which tries to use it to access the line at that location in memory. Fun comes.
Two important things to keep in mind:
If your compiler prints warnings, pay close attention to them. gcc in particular warns of a lot that IMHO should be fatal errors. Never ignore warnings if you do not understand what warning means, enough for your knowledge to redefine the capabilities of the compiler authors.
Arrays and pointers are two different things. Several C language rules seem compliant to make them look as if they are the same. You can temporarily walk away with the assumption that arrays are nothing more than masking pointers, but this assumption will eventually come back to bite you. Read section 6 of the comp.lang.c FAQ ; he explains the relationship between arrays and pointers better than I can.