All three examples are technically incorrect; given a character code in an int
variable i
, the correct way to print it with printf
is
printf("%c\n", i); // pass the _value_, not the address of i
(You might be confused because when you use scanf
you do need to pass the address of i
. That's because scanf
writes to the variable. "Out parameters" in C always need to be pointers.)
The first example is also incorrect because it's dangerous to use anything but a string literal as the first argument to printf
. (There are times when you have to pass a string that varies at runtime, but don't worry about that until you trip over a situation that requires it.)
Anyway, let's peel back the "undefined behavior" veil and look at what's actually happening. This modification of your second example
unsigned int i = 'a'; // == 97, assuming ASCII
printf("%s\n", (char *)&i);
does not have undefined behavior (merely "implementation defined" behavior) and should, on almost all modern computers, compile to exactly the same code as your second example. On a majority of modern computers it will print "a" and on the remainder it will print a blank line. Here's why: int
is almost certainly either two or four bytes. When you store 'a'
in an int
, the numeric value of the character constant (let's go ahead and assume ASCII, so 97 == 0x61) will be zero-extended to the width of int
, either 0x0061 or 0x00000061. When you take the address of the variable you force it into memory, where it will be stored as a sequence of bytes. But in what order? A majority of computers nowadays are "little endian", meaning that the lowest-value bits of the number are stored at the lowest address, so we have either the byte sequence 0x61 0x00 or 0x61 0x00 0x00 0x00. And that is identical to the byte sequence you'd have in memory for the C string "a"
, possibly followed by some junk, which is what you told printf it was to print.
The alternative to little-endian is big-endian, in which the lowest-value bits are stored at the highest address; in that case you'd have [0x00 0x00] 0x00 0x61 and printf
would see that leading 0x00 and interpret it as having been passed an empty string and so it would only print a blank line.
More or less the same thing may have happened with your first example, since either "a"
or ""
is a format string with no substitutions if used as the first argument to printf
, and extra arguments to printf
are ignored.
The third example, on the other hand, hands printf
a pointer value (we have no way of knowing what the actual number is) and tells it to interpret that as a character constant. Depending on how the "calling convention" works on your computer, printf
might not even look for the number where the caller put it! All we can say about this is, it's very likely not to print a
, because the %c
format code is never going to dereference the pointer.