1

I am running the following code snippet:

#include <stdio.h>
int main() {
   printf("%f %d\n", 42, 3.14);
}

Which, to my astonishment, displays:

3.140000 42

Compiler (gcc 8.3.0 on a Debian-based distro) does warn me about the order of the arguments:

test.c: In function ‘main’:
test.c:3:13: warning: format ‘%f’ expects argument of type ‘double’, but argument 2 has type ‘int’ [-Wformat=]
printf("%f %d\n", 42, 3.14);
        ~^        ~~
        %d
test.c:3:16: warning: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘double’ [-Wformat=]
printf("%f %d\n", 42, 3.14);
           ~^         ~~~~
           %f

Can a soul more enlightened than mine explain me this behavior? I have found nothing in the specification that would explain it.

wecx
  • 302
  • 2
  • 10
  • 2
    Possible duplicate of [Undefined, unspecified and implementation-defined behavior](https://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior). – John Kugelman Oct 21 '19 at 21:34
  • 2
    There's no explaining undefined behavior. The compiler told you that the code is wrong, so fix it. – user3386109 Oct 21 '19 at 21:41
  • @user3386109: It is not true there is no explaining undefined behavior, especially for open-source compilers. In the C standard, “undefined behavior” **only** means that **the C standard** does not impose requirements on the behavior. It does not mean **nothing** affects what behavior will occur. And, we see from an answer in this case, the behavior is explainable, even obvious to people familiar with common schemes for passing arguments. – Eric Postpischil Oct 21 '19 at 22:45
  • When the compiler outputs a warning message, then the compiler is allowed to do what ever it can to work around the problem. Some times, the work around is acceptable (but not always) Suggest you pay attention to the warnings, It is easy to fix most warnings. So, fix those warnings and move on – user3629249 Oct 22 '19 at 04:20

2 Answers2

7

On your machine, it just so happens that the standard calling convention dictates that floating-point arguments are passed to functions in a separate area from integer and other arguments. Your printf format string is interested in the first floating-point argument and the first non-floating-point argument, and it finds both of them in the expected location, even though you passed them in the wrong order. This is not something you should count on, of course; it could stop working if you compile for a different machine, or with some very small changes to your code.

hobbs
  • 223,387
  • 19
  • 210
  • 288
1

Adding detail to @hobbs answer for the of x86-64 architecture. x86-64 function calling convention is that the first six integer or pointer parameters are passed in registers

%rdi, %rsi, %rdx, %rcx, %r8, and %r9

whereas the first sixteen floating point parameters are passed in registers

%xmm0 -%xmm15

In this case the first printf parameter is the format string which is a pointer so it will be passed in %rdi, the second argument is an integer so it will be passed in %rsi. The third argument is a floating point number so it will be passed in register %xmm0.

Inside printf the first argument, the format string will be read from %rdi, now it will process the format string and find "%f", so it will read %xmm0 because that's where the first floating point argument is supposed to reside, it finds "3.14" in %xmm0 and prints it correctly then it looks at the format string again and finds %d so it reads the register %esi, which will have the second integer argument, it finds 42 there and prints it correctly.

Since there are six registers for passing integers/pointers and 16 for passing floating points the following also works

int main() {
   printf("%d %d %d %d %d %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f\n",
           1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0, 40, 41, 42,
           43,44);
}

Output: 40 41 42 43 44 1.000000 2.000000 3.000000 4.000000 5.000000 6.000000 7.000000 8.000000 9.000000 10.000000 11.00000000 12.000000 13.000000 14.000000 15.000000 16.000000

AliA
  • 690
  • 6
  • 8