Reputation: 27230
I have a couple of programs whose output I cannot understand:
#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
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
What is going on here? Why am I getting the outputs shown?
Upvotes: 0
Views: 1274
Reputation: 136425
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:
printf("i is %f \n",i);
prints the value from XMM0 register, which happens to be the value of k
assigned earlier and not i
passed in RSI register. Assembly looks like this:
movl $.LC1, %edi # "k is %f \n"
movsd .LC0(%rip), %xmm0 # float k = 2
call printf
movl $1, %esi # int i = 1
movl $.LC2, %edi # "i is %f \n"
call printf # prints xmm0 because of %f, not esi
If you reorder the assignments like this:
int i = 1;
printf("i is %f \n",i);
float k = 2;
printf("k is %f \n",k);
It prints:
i is 0.000000
k is 2.000000
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 casting int*
to double*
and then reading that double
. Let's modify the example to make it easy to see:
int main() {
float k = 2;
int i = -1;
printf("k is %f \n",k);
printf("i is %f \n",i,i);
}
64-bit output:
k is 2.000000
i is 2.000000
32-bit output:
k is 2.000000
i is -nan
That is, 2 int
s with value of -1 look like a double
0xffffffffffffffff which is a NaN
value.
Upvotes: 8
Reputation: 651
It is even more interesting. I tested it on three different linux machines and got two different results for second output line:
gcc 4.5.1 32bit: a is -0.000000
gcc 4.5.1 -Ox 32bit: a is 0.000000
gcc 4.5.1 64bit: a is 0.000000
gcc 4.6.2 64bit: a is 0.000000
Upvotes: 1
Reputation: 17157
printf("i is %d \n",i);
Nothing weird here. The character a
is number 97 in ASCII.
printf("a is %f \n",a);
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 that a
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.
printf("a is %f \n",(float)a);
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.0000
Follow-up cool experiment: Want to see something else neat? Try
union data {
int i;
float f;
char ch[4];
};
union data d;
d.i = 5;
printf("How 5 is represented in memory as an integer:\n");
printf("0x%X 0x%X 0x%X 0x%X\n", v.ch[0], v.ch[1], v.ch[2], v.ch[3]);
d.f = 5.0;
printf("How 5 is represented in memory as a float:\n");
printf("0x%X 0x%X 0x%X 0x%X\n", v.ch[0], v.ch[1], v.ch[2], v.ch[3]);
// OUTPUT:
// How 5 is represented in memory as an integer:
// 0x5 0x0 0x0 0x0
// How 5 is represented in memory as a float:
// 0x0 0x0 0xFFFFFFA0 0x40
You can actually see how the C compiler changes the data around for you (assuming this works...)
Upvotes: 1
Reputation: 754520
First, with any variadic function such as printf()
, all integer values of a type shorter than int
are passed as int
(or unsigned int
in some cases on some platforms), and all float
values are passed as double
. Your (float)a
therefore is cast a second time to double
.
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 tell printf()
to expect a double
, printf()
will try to read a double
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.
Upvotes: 8
Reputation: 3902
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.
Upvotes: 2