0

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?

iBug
  • 35,554
  • 7
  • 89
  • 134
  • 2
    The second line invokes undefined behaviour; anything can happen and it is OK. Somewhat to my surprise, I am able to reproduce the problem, using GCC 8.3.0 on a RHEL 5 platform. – Jonathan Leffler Apr 13 '19 at 05:57
  • actually if you compile with -m32 you get different output.. 1234.000000 5678..... 0.000000 1083394048 – simptri Apr 13 '19 at 05:58
  • 2
    @user3386109 That duplicate doesn't answer anything. – iBug Apr 13 '19 at 06:03
  • 5
    It answers your question. If the format you provide to `printf` doesn't match the arguments, the result is undefined behavior. End of story. There's nothing more to say. – user3386109 Apr 13 '19 at 06:05
  • In this specific case, the undefined behavior works out the way it does due to the x86_64 ABI. – Chris Dodd Apr 13 '19 at 06:45

1 Answers1

4

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?

iBug
  • 35,554
  • 7
  • 89
  • 134