What happens to a float variable when %d is used in a printf?
It is possible to print any integer value one wants, regardless of the floating point parameter:
printf("A: %d B: %6.2f\n", f, f + 0.15);
Here is how you can print arbitrary integers on Intel architecture:
int print_it(int, int /* nameless but printed */, float f)
{
printf("A: %d B: %6.2f\n", f, f + 0.15);
}
int main()
{
print_it(0, 12 /* will be printed */, 0.0);
print_it(0, 123 /* printed */, 1.1);
print_it(0, 1234 /* printed */ , 2.2);
}
This output:
A: 12 B: 0.00
A: 123 B: 1.10
A: 1234 B: 2.20
Explanation: Obviously, mismatched format string and parameters lead to undefined behavior. Nevertheless, sometimes this can be predicted. On Intel architecture, the first few parameters are passed by registers. Floating point values are passed on different registers.
Despite having the same printf
instruction as in the question, the output is different. What happens is that 12, 123, 1234 are passed through the general purpose register responsible for the second non-floating point parameter. Since printf
has only one non-floating point parameter, the register of the second non-fp parameter is unchanged. This register retains the value it got from the second parameter of print_it(0, int_value, fp_value)
.
But the original gives garbage:
for (f = 0.0; f <= 3; f += 1.1) printf("A: %3f B: %6.2f\n", f, f + 0.15);
It gives different garbage because printf
calls other functions internally. These functions trash the general purpose register that printf("... %d ...", ...)
reads.
Obviously, this behavior happens only on systems that pass floating point parameters in a separate set of registers. Obviously, this happens only if the compiler optimization does not modify the code in some way, because it is allowed to do wild things when undefined behavior is at play.
Printf will treat the memory you point as however you tell it to. There is no conversion going on. It is treating the memory that represents the float as an int. Because the two are stored differently, you get what is essentially a random number.
If you want to output your float as an integer, you should cast it first:
printf("A: %3d B: %6.2f\n", (int)f, f + 0.15);
When you called:
printf("A: %3d B: %6.2f\n", f, f + 0.15);
C automatically converts the float
values to double
(it is a standard conversion made when you call a function that takes variable arguments, such as int printf(const char *fmt, ...);
). For sake of argument, we will assume that sizeof(int)
is 4 and sizeof(double)
is 8 (there are exceptions, but they are few and far between).
The call, therefore, has pushed a pointer onto the stack, plus an 8-byte double for f
, and another 8-byte double for f + 0.15
. When it is processing the format string, the %d
tells printf()
that you pushed a 4-byte int
onto the stack after the format string. Since that is not what you did, you have invoked undefined behaviour; whatever happens next is OK according to the C standard.
However, the most likely implementation blithely reads 4 bytes and prints them as if they were an int
(it trusts you to tell it the truth). Then it comes across the %6.2f
format; it will read 8-bytes off the stack as a double
. There's an outside chance that this would cause a memory fault for misaligned access (it would require a 64-bit machine with a requirement that double
be aligned on an 8-byte boundary, such as a SPARC), or it will read 4 bytes from f
and 4 bytes from f + 0.15
, putting them together to create some rather unexpected double
value -- as your example shows.