I have a small question. I have this piece of code:
#include
int main(){
printf(\"%d, %f, %d\\n\", 0.9, 10, \'C\');
}
And
But I would have expected: …
When the format string does not match the types of the arguments in order, the behavior is undefined. Anything can happen. You cannot expect anything (and I do not see why you would expect 0. Maybe you expect printf
to use the format string to convert the arguments between floating-point and integer. It just doesn't. It's a variadic function like you could write your own, and the fact that the format string encodes the types of the trailing arguments is not used to convert them).
Modern calling conventions use registers for the first few arguments, and can use registers even for variadic functions. On x86-64, the convention can be for instance that the first floating-point argument to a variadic function can always be expected in the floating-point register xmm0
, whereas integer arguments are passed in general-purpose registers %rdi
, %rsi
, %rdx
, … This has the effect that printf("%f %d", 1, 1.0)
prints the floating-point argument followed by the integer argument.
As an illustration, here is a short program:
#include <stdio.h>
int main(){
printf("%d, %f, %d\n", 0.9, 10, 'C');
printf("%d, %f, %d\n", 10, 0.9, 'C');
}
This is how my compiler (Clang on Mac OS X 10.6) compiles the program:
leaq <memory location of format string>, %rbx
movq %rbx, %rdi
movsd <memory location of 0.9 constant>, %xmm0
movl $10, %esi
movl $67, %edx
movb $1, %al
callq _printf
movq %rbx, %rdi
movl $10, %esi
movsd <memory location of 0.9 constant>, %xmm0
movl $67, %edx
movb $1, %al
callq _printf
…
It is clear that the two calls produce the same result. But in the case of one, it is accidental and only “works” for this particular compiler version and ABI, whereas the other one respects the standard and has to work anywhere.
Again, printf("%d %f", 0.9, 10)
is undefined behavior and you should not use it in any circumstances.
The only correct expectation is getting 67
for printing a character using %d
format specifier*. The other two printouts are undefined behavior.
it looks like the printf searches for the corresponding type in the expressions
This is only a coincidence. printf
has no idea of the types of the actual parameters that you pass. It trusts the format string, and interprets the data sequentially. You can tell what's going on by supplying different numbers, and observing how the output changes.
The numbers that you see is garbage - a double
re-interpreted as an int
, and an int
re-interpreted as a double
. Moreover, if the sizes of double
and int
are different, the first two parameters cross each others' boundaries.
To produce the output that you want add a cast to the first two parameter expressions:
printf("%d, %f, %d\n", (int)0.9, (double)10, 'C');
Note that you do not need to cast the last parameter, because char
is promoted to an int
as part of processing variable length argument list of printf
.
* This produces correct behavior only when there are no mismatches between other parameters and format specifiers; your program has UB even for the last parameter which would be correct if used by itself.
It is undefined behaviour, so anything can happen.
%d
requires an int
, but you are passing a double
(not a float
), so printf
takes 4 bytes of the double
value and interprets it as an int
.
%f
requires a double
, but you are passing an int
. So it takes the 4 byte of the int
and 4 bytes from the next memory and interprets it as a double
.
You are lucky to pass 16 bytes while printf
expects 16 bytes, so the last value is the correct one.