Moving printf to different lines gives different outputs? (WITH)

In C, when I move this line printf: printf("%f\n", 5 / 2); in different lines, its output changes. Any ideas?

Here is 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 the conclusion is:

 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); on one of the first lines (between the one that A prints out and the one that prints 65.000000), it will print 0.000000 (which makes sense) instead of 2.500000 now. Any ideas?

+5
source share
2 answers

Your code causes undefined behavior.

You must use the correct data specifiers to print things in printf , otherwise you call UB. Thus, it doesn’t matter or surprise you get different results in different places.

http://en.cppreference.com/w/c/io/fprintf

If the conversion specification is not valid, the behavior is undefined.

The same is true only for c.

When invoking undefined behavior, the results are by definition random and unpredictable, so asking us to predict them makes no sense.

+4
source

As commentators note, the line printf("%f\n", 5 / 2); just demonstrates undefined behavior. But let's see why you can get such results on the x86-64 architecture using System V ABI.

The short answer is that the first few arguments are passed through registers. The choice depends on the type of argument: integer arguments go to the "classical" registers ( edi , esi , etc.), Floating point go to the SSE registers ( xmm0 , xmm1 , etc.).

Since we put the wrong type in the format string, printf reads the argument from the wrong case.


Simplify your program as follows:

 #include <stdio.h> int main(void) { printf("%f\n", 5/2); printf("%f\n", 5.0/2); printf("%f\n", 5/2); return 0; } 

Now let's move on to disassembling main . We start with the prologue function, which is not 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 receives a pointer to a 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 format "%f\n" and try to read the argument from xmm0 . In my case, this register has a value of 0 , so this outputs 0.000000 .

In the second call, the argument is obviously a floating point number that goes 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 the expected 2.500000 (which you see here as 0x4004000000000000 , which corresponds to the 64-bit floating point constant for 2.5). We pass it through xmm0 , and it reads it from xmm0 .

The third call exactly matches the first:

  mov $0x2,%esi mov $0x4005e4,%edi mov $0x0,%eax callq 4003e0 < printf@plt > 

What has changed is that printf calls did not change the value in xmm0 . It still contains the constant 2.5 from the second call, since we call printf 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 
+3
source

Source: https://habr.com/ru/post/1235878/


All Articles