cprintfvariadic-functions

printf() giving identical output on x86-64 platforms even when arguments are swapped


Consider the following code:

#include <stdio.h>

int main() {
    printf("%lf %ld\n", 1234.0, 5678L);
    printf("%lf %ld\n", 5678L, 1234.0);
}

Both calls to printf prints the same text 1234.000000 5678, which doesn't very well match up with the code on the second call (should probably have been 5678.0000 1234).

I'm on Linux 4.x on a x86-64 processor, but I am not able to reproduce this on x86 (32-bit). I suppose it's reproducible on any Linux system on amd64 architecture.

Why does swapped arguments give the same output for printf, and why is it specific to x86-64?


Solution

  • The answer is because it's how the System V ABI x86-64 defines how arguments should be passed.

    According to PDF page 22, the first 6 integer arguments are passed on %rdi, %rsi, %rdx, %rcx, %r8, %r9, and the first 8 floating-point arguments are passed from %xmm0 to %xmm7. However, there's no specific order between integers and floats. Therefore, the following two functions, despite being defined differently, behave the same.

    int f1(int i1, int i2, int i3, double d1, double d2, double d3);
    int f2(double d1, double d2, int i1, int i2, double d3, int i3);
    

    As compiled following the Syetem V x86-64 ABI, both functions will receive i1, i2 and i3 in registers %rdi, %rsi and %rdx, and d1, d2 and d3 in registers %xmm0, %xmm1, %xmm2.

    Variadic arguments are no exception. Up to 6 integers and up to 8 floats are passed via registers, and the rest are passed on stack.

    Talking about this specific code, by inspecting the assembly code generated by gcc -O0 -S, I verified the above statements: The integer 5678 is sent to printf via %rsi, and the (double-precision) floating-point value 1234.0 is sent to printf via %xmm0. In both cases, %eax is set to 1, indicating there's one floating-point argument available.

    Oh yeah where's %rdi? Actually, the formatting string is the first argument, so a pointer to the string is passed via %rdi.

    printf doesn't know if the integer is before the float or the other way, it only knows it has one integer argument (after the formatting string) and one floating-point argument (reading %al). This is exactly why the two lines produce identical output.

    TODO: someone put a Godbolt link here?