clinuxgcc

Strange behavior of printf when converting a pointer to a double


I was trying to demonstrate what happens if you forget to dereference a pointer by printing a pointer to double double *pd with printf("%f", pd) (instead of using *pd).

I was expecting this to produce a random double value (the double representation of the bit pattern of the address in the pointer).

I was suprised to find that whatever double value I print before that affects the value printed when "forgetting" to dereference the pointer.

Here is the program:

#include <stdio.h>

int main() {

    double d = 123.456, *pd = &d;

    printf("%f ", *pd); // first print
    printf("%f\n", pd);

    return 0;
}

I was suprised to get results like this:

123.456000 123.456000

Now, if I comment the first print, I get this:

0.000000

In fact, whatever double value I print in the first print, gets printed as the double interpretation of the bit pattern of the pointer... for example, this code:

    printf("%f ", 555.555);
    printf("%f\n", pd);

Produced this:

555.555000 555.555000

It looks like this is somehow connected to old left-over values on the stack, but since a pointer is 8 bytes, and a double is also 8 bytes, I don't understand why this is happening.

I'm running on Ubuntu 22.04 with gcc version 11.4.0, compiling with:

$ gcc -O0 example.c -o example

Any idea what is going on?


Solution

  • This is a common behavior on systems that pass floating-point arguments in separate registers from integer and pointer arguments.

    Typically, the specification for how routines pass arguments to other routines (which is part of a document called an application binary interface) says that the first integer/pointer argument is passed in some general register R4 (for illustration; the names and numbers vary in different platforms), the second integer/pointer argument is passed in R5, and so on, and the first floating-point argument is passed in F0, the second is passed in F1, and so on. (Once the designated registers are used up, further arguments are passed on the stack.)

    When printf("%f ", *pd); is executed, the address for "%f " is loaded into the appropriate register (R4 in this example), the double for *pd is loaded into its register (F0), and printf is called.

    When printf("%f\n", pd); is executed, the address for "%f\n" is loaded into R4. Then, since pd is a pointer, it is loaded into the next register for integer/pointer arguments, R5. The F0 register is left untouched. Then printf is called. printf sees the %f, so it expects a double argument and gets it from F0. Since that register still has the previous double value, that is what is printed.

    This is fragile. printf might use the registers while it is formatting numbers to be printed, so it might not leave the previous value in F0. Optimization by the compiler might change various things around the printf statements so that the value is not left in F0 after the first printf call and before the second. The platform you are on might pass arguments differently than this example. But, when you see this behavior, that is what is likely happening.