Reputation: 55
In C, when I move this printf line: printf("%f\n", 5 / 2);
to different lines its output changes. Any ideas?
Heres the code:
#include <stdlib.h>
#include <stdio.h>
int main()
{
int a = 65;
char c = (char)a;
int m = 3.0/2;
printf("%c\n", c);
printf("%f\n", (float)a);
printf("%f\n", 5.0 / 2);
printf("%f\n", 5 / 2.0);
printf("%f\n", (float)5 / 2);
printf("%f\n", 5 / (float)2);
printf("%f\n", (float)(5 / 2));
printf("%f\n", 5.0 / 2);
printf("%d\n", m);
printf("%f\n", 5 / 2);
system("PAUSE");
return(0);
}
And heres the output:
A
65.000000
2.500000
2.500000
2.500000
2.500000
2.000000
2.500000
1
2.500000
And if I move printf("%f\n", 5 / 2);
to one of the first lines (between the one that outputs A and the one that outputs 65.000000) it will print 0.000000 (which makes sense) instead of its now 2.500000.
Any ideas?
Upvotes: 5
Views: 835
Reputation:
As commenters have indicated, the line printf("%f\n", 5 / 2);
simply exhibits undefined behaviour. But let's see why you might get results like these on an x86-64 architecture using the System V ABI.
The short answer is that the first few arguments are communicated through registers. The choice depends on the type of argument: integer arguments go in "classic" registers (edi
, esi
, etc.), floating-point go in SSE registers (xmm0
, xmm1
, etc.).
Because we're giving the wrong type in the format string, printf
is reading the argument from the wrong register.
Let's simplify your program to the following:
#include <stdio.h>
int main(void)
{
printf("%f\n", 5/2);
printf("%f\n", 5.0/2);
printf("%f\n", 5/2);
return 0;
}
Let's now walk through the disassembly of main
. We start off with the function prologue, which isn't too special:
push %rbp
mov %rsp,%rbp
sub $0x10,%rsp
Then, we get our first call to printf
, where the arguments are passed to edi
(which gets a pointer to the format string) and esi
(5/2
, which is 2
due to integer division):
mov $0x2,%esi
mov $0x4005e4,%edi
mov $0x0,%eax
callq 4003e0 <printf@plt>
However, printf
will read the "%f\n"
format and try to read the argument from xmm0
. In my case, this register has the value 0
, so this prints out 0.000000
.
In the second call, the argument is obviously a floating-point number, which gets passed through xmm0
:
movabs $0x4004000000000000,%rax
mov %rax,-0x8(%rbp)
movsd -0x8(%rbp),%xmm0
mov $0x4005e4,%edi
mov $0x1,%eax
callq 4003e0 <printf@plt>
Now, printf
prints out the expected 2.500000
(which you see here as 0x4004000000000000
, which is what the 64-bit floating-point constant for 2.5 looks like). We pass it through xmm0
, and it reads it from xmm0
.
The third call is exactly the same as the first:
mov $0x2,%esi
mov $0x4005e4,%edi
mov $0x0,%eax
callq 4003e0 <printf@plt>
What has changed is that the calls to printf
didn't change the value in xmm0
. It still contains the constant 2.5 from before the second call as we call printf
for the third time. In the third call, printf
will print 2.500000
again.
(And our function ends with a boring return 0
, of course:)
mov $0x0,%eax
leaveq
retq
Upvotes: 3
Reputation: 7352
Your code is invoking undefined behavior.
You are obligated to use the correct data specifiers for printing things in printf
, and failing to do so is invoking UB. Thus it is unimportant and not surprising that you get different results at different places.
http://en.cppreference.com/w/c/io/fprintf
If a conversion specification is invalid, the behavior is undefined.
The same holds true for just c.
When invoking undefined behavior, the results are by definition random and not predictable, so asking us to predict them makes little sense.
Upvotes: 4