Julio Vga
Julio Vga

Reputation: 163

Why printf takes the last number printed?

EDIT: I already knew that printf is not typesafe I am just looking for an explination about what exactly ocurred (I mean describe the undefined behavior).

Why if I print "7" in the second printf, the program prints 9.334354. I know that if I don't write 7.0, this won't be printed but Why does it take the first number writted instead?.

#include <stdio.h>  

int main()  
{  
    printf("%.2f\n", 9.334354);    
    printf("%.5f\n", 7);  
    printf("%03d\n", 9);  
    getchar();  
}

This is the output

    9.33
    9.33435
    009

Upvotes: 5

Views: 790

Answers (6)

Kerrek SB
Kerrek SB

Reputation: 477610

Repeat this to yourself for two weeks once a night before going to bed:

printf is not typesafe. printf is not typesafe. printf is not typesafe.

The function will only work if you pass it an argument of the type that you promise. Everything else is undefined behaviour. You promise a double (via %f) but provide an int (the type of the literal 7), so it's undefined behaviour. Shame on you.

(I did once go into details to explain the actual output, in case you're interested.)


Update: Since you're interested in the explanation for this particular behaviour, here's the (relevant) assembly for that code on my x86/GCC4.6.2/-O3:

First the data sections:

.LC0:
        .long   1921946325
        .long   1076013872   // 0x 4022AB30 728E92D5 is the binary rep of 9.334354
.LC1:
        .string "%.2f\n"
.LC2:
        .string "%.5f\n"
.LC3:
        .string "%03d\n"

Now the code:

        fldl    .LC0              // load number into fp register
        fstpl   4(%esp)           // put 64-bit double on the stack
        movl    $.LC1, (%esp)     // first argument (format string)
        call    printf            // call printf

        movl    $7, 4(%esp)       // put integer VA (7) onto stack
        movl    $.LC2, (%esp)     // first argument (format string)
        call    printf            // call printf

        movl    $9, 4(%esp)       // put integer VA (9) onto stack
        movl    $.LC3, (%esp)     // first argument (format string)
        call    printf            // call printf

The reason you see what you see is simple now. Let's for a moment switch to full 17-digit output:

  printf("%.17f\n", 9.334354);
  printf("%.17f\n", 7);

We get:

9.33435399999999937
9.33435058593751243

Now let's replace the integer by the "correct" binary component:

 printf("%.17f\n", 9.334354);
 printf("%.17f\n", 1921946325);

And voila:

9.33435399999999937
9.33435399999999937

What happens is that the double occupies 8 bytes on the stack, of value 0x4022AB30728E92D5. The integer only occupies 4 bytes, and as it happens, the least significant four bytes are overwritten, so the floating point value is still nearly the same. If you overwrite the four bytes with the same bytes that occur in the original float, then you get the exact same result.

I might add that it's pure luck that the most significant four bytes remain intact. In different circumstances, they might have been overwritten with something else. In short, "undefined behaviour".

Upvotes: 16

Alex Measday
Alex Measday

Reputation: 231

As Barry Brown hinted at, your 7 is overwriting 4 bytes of the 8-byte double previously stored on the stack. Depending on which way the stack grows and the endianness of your doubles, the 7 might just be overwriting the least significant bits of the double's mantissa. Consequently, the double on the stack is not actually identical to that in the previous call to printf(); it's just that the difference is not visible in the %.5f format.

Printf() is not strictly typesafe, but some compilers check the format statement against the arguments and will warn you if you do something like this.

Upvotes: 2

tangrs
tangrs

Reputation: 9940

7 is an integer. 7.0 is a float/double.

printf isn't typesafe so it's expecting a double but you're passing it an integer. That means undefined behavior.

Upvotes: 0

Barry Brown
Barry Brown

Reputation: 20634

7 is an int, but printf is expecting the argument to be a float. The argument from the previous printf (9.334354) is still sitting on the stack and the 7 alone isn't big enough to overwrite it.

If you change the 7 to 7.0, it works correctly.

Upvotes: 1

Seth Carnegie
Seth Carnegie

Reputation: 75150

You're using the wrong format specifiers. You're passing a float (which turns into a double) so printf is expecting 8 bytes on the stack, but you pass an int, which is 4 bytes. Write 7.0 and 9.0 to turn the literals into doubles. Or, as Daniel said in the comments, the optimiser could cause all sorts of weird behaviour to occur.

Upvotes: 2

Daniel Pryden
Daniel Pryden

Reputation: 60997

You're telling printf that you're passing it a double (because of the f format specifier) but you're actually passing it an int. That's undefined behavior, and literally anything could happen.

Upvotes: 1

Related Questions