I met a problem when I test the function printf:
First I write code like this:
int main(void)
{
char a = 'a';
printf("a = %f\n", a);
return 0;
}
The output is
And then I write code:
int main(void)
{
float b = 'a';
printf("b = %f\n", b);
return 0;
}
The output is
And then I write code:
int main(void)
{
char a = 'a';
float b = 'a';
printf("b = %f\n", b);
printf("a = %f\n", a);
return 0;
}
The output is
So I am confused why in the first program a
= 0.000000
and in the third program a
= 97.000000
?
How does the function printf()
work?
How does the symbol %f
, %d
work?
Update: after doing some more research on this, it seems that the differences between the float
and int
memory representations are not the ones responsible for the behaviour of the three programs.
I've looked at the object code for the third program and I found the cause of the odd behaviour: the floating point arguments are sent to other registry/stack than the integer ones. And printf
relies on that and looks them up in other locations than the ones the callee of printf
(i.e. the main
method) places the arguments.
Here's the relevant disassembly of the the third program (for an x86_64 architecture):
0000000100000f18 leaq 0x71(%rip), %rdi ## literal pool for: "b = %f\n"
0000000100000f1f movsd 0x61(%rip), %xmm0 ## b variable gets sent to xmm0
0000000100000f27 movl $0x0, -0x4(%rbp)
0000000100000f2e movb $0x61, -0x5(%rbp) ## a variable gets placed on the callee stack
0000000100000f32 movsd %xmm0, -0x10(%rbp)
0000000100000f37 movsd -0x10(%rbp), %xmm0
0000000100000f3c movb $0x1, %al
0000000100000f3e callq 0x100000f66 ## symbol stub for: _printf
0000000100000f43 leaq 0x4e(%rip), %rdi ## literal pool for: "a = %f\n"
0000000100000f4a movsbl -0x5(%rbp), %esi
0000000100000f4e movl %eax, -0x14(%rbp)
0000000100000f51 movb $0x0, %al
0000000100000f53 callq 0x100000f66 ## symbol stub for: _printf
And printf
relies on this, it assumes the callee has placed %f
arguments in the xmm0
/xmm1
/etc registers, and the behaviour of the three programs is like this:
printf
looks for the %f
argument in the xmm0 register, however as we're at the start of the program, the register is clean and main
has placed a
into eax
, thus xmm0
holds a zero value, and this is what printf
printsmain
correctly places b
in xmm0
, and printf
takes it from there, printing the correct valueb
is printed first, the xmm0
register will hold this value, and since printf
doesn't mess with the register, when it's called the second time it fetches again from xmm0
that remained intact after the first printf
call.So it's all about caller/callee conventions on where integers and floats are being send by the caller and from where the callee tries to pick them up.
Original response: In the first program you are trying to print a float, but you pass an int (char is a smaller int). Due to the fact that ints and floats have different binary representations, the int 97 (corresponding to the character 'a') corresponds to a very small float: 1.36E-43, that gets printed as zero.
Here is the binary representation of 97 (the compiler expands any 1-byte char to a 4-byte argument when calling a function)00000000 00000000 00000000 01100001
IEEE 754 is the standard format for binary representations of float/double numbers, you can play with an online converter here, and you can see how the same binary number has different values when its interpreted as an int or as a float.
The %f
here represents a replacement token for a float.
In order to replace a char you'd need %c
.
Here is a list that tells you what is the appropriate replacement token for each type.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With