SpacePotato
SpacePotato

Reputation: 55

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

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

Answers (2)

user824425
user824425

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

Magisch
Magisch

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

Related Questions