I have a couple of programs whose output I cannot understand:
Program 1
#include <stdio.h>
int main(void)
{
int i=1;
float k=2;
printf("k is %f \n",k);
printf("i is %f \n",i);
return 0;
}
Output is at http://codepad.org/ZoYsP6dc
k is 2.000000
i is 2.000000
Program 2
Now one more example
#include <stdio.h>
int main(void)
{
char i='a';
int a=5;
printf("i is %d \n",i); // here %d type cast char value in int
printf("a is %f \n",a); // hete %f dont typecast float value
printf("a is %f \n",(float)a); // if we write (float) with %f then it works
return 0;
}
Here output is at http://codepad.org/XkZVRg64
i is 97
a is 2.168831
a is 5.000000
Question
What is going on here? Why am I getting the outputs shown?
You are probably running this application on a 64-bit x86 architecture. On this architecture floating point arguments are passed in XMM registers, whereas integer arguments get passed in the general purpose registers. See System V AMD64 ABI convention.
Because
%f
expects a floating point value:prints the value from XMM0 register, which happens to be the value of
k
assigned earlier and noti
passed in RSI register. Assembly looks like this:If you reorder the assignments like this:
It prints:
Because XMM0 register happens to have value of 0.
[Update] It is reproducible on a 32-bit x86 as well. On this platform
printf()
is basically castingint*
todouble*
and then reading thatdouble
. Let's modify the example to make it easy to see:64-bit output:
32-bit output:
That is, 2
int
s with value of -1 look like adouble
0xffffffffffffffff which is aNaN
value.Nothing weird here. The character
a
is number 97 in ASCII.a
is an int with the value 5. In memory, that'll be the bytes[0x5 0x0 0x0 0x0]
. This command lies to printf, though. It says "just trust me thata
points to a float". printf believes you. Floats are pretty weird, but basically they work like scientific notation but in base 2 instead of base 10. By random chance, the way that you specify 2.1688 as a float happens to be[0x5 0x0 0x0 0x0]
. So that's what printf shows you.Here you've told the compiler you want to convert
a
into a float before printf sees it. The C compiler knows how to change things to express 5 in the weird float format. So printf gets what it expects, and you see 5.0000Follow-up cool experiment: Want to see something else neat? Try
You can actually see how the C compiler changes the data around for you (assuming this works...)
It is even more interesting. I tested it on three different linux machines and got two different results for second output line:
The compiler does not do any type conversions based on format specifiers like
%f
. Each parameter is passed in the normal manner, and giving an argument that does not match its format specifier is a bug in your program resulting in undefined behaviour.First, with any variadic function such as
printf()
, all integer values of a type shorter thanint
are passed asint
(orunsigned int
in some cases on some platforms), and allfloat
values are passed asdouble
. Your(float)a
therefore is cast a second time todouble
.Secondly,
printf()
itself takes you at your word. If you pass garbage in, you get garbage out. More accurately, if you pass an integer where you tellprintf()
to expect adouble
,printf()
will try to read adouble
from the parameter list. What happens next is undefined behaviour.Some compilers - notably GCC - will report type mismatches between literal string formats and the corresponding parameter list. If your regular compiler does not do that analysis, consider using a compiler that will - at least for preliminary compilations to sort out the compilation errors.