Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange behaviour of two successive printf() calls in C

Tags:

c

printf

I was playing with C; check this out:

#include <stdio.h>
#include <stdlib.h>

void main() {
    printf("%d\n", 1.5);
    printf("%f", 0);
}

I expected the output:

0
0.000000

but it prints:

0
1.500000

Did the first printf() pass the 1.5 to second printf()?

PS: I'm aware of ( %d for ints, %f for floats ). As I mentioned, I was just messing with the code.

PS2: I'm using DevC++ & Code::Blocks.

like image 421
April Johnson Avatar asked Jul 02 '17 14:07

April Johnson


2 Answers

The behavior is undefined as per the C Standard, here is what may be happening on your system:

  • For the first call printf("%d\n", 1.5); main passes the floating point value 1.5 as a double in the first XMM register and call printf().
  • printf() does not modify the XMM register because if does not perform any floating point operation to handle the format "%d". It retrieves the value to print from a different place: a register or the stack, and this value happens to be 0.
  • for the second call printf("%f", 0);, main does not change the XMM either because it passes an int value of 0 via the other place, a register or the stack.
  • the second printf() finally gets the double value for the format %f from the XMM register where 1.5 has been stored earlier. Hence the output 1.500000.

None of the above is guaranteed in any way, but this is probably the explanation you are interested in. Different systems may handle parameter passing in different ways, they are part of the ABI (Application Binary Interface).

Just for fun, you might want to try this variation:

printf("first %d, second %f\n", 1.5, 42);

which on my system outputs first 42, second 1.500000

like image 88
chqrlie Avatar answered Sep 28 '22 15:09

chqrlie


You've got some undefined behavior (so arbitrarily bad things could happen and you should not expect anything good). With %f the printf function expects a double (notice that when passed as argument a float gets promoted to a double) but 0 is a literal of type int. Also, different compilers (even different versions of the same compiler) or different optimization flags could produce different bad effects.

Please read Lattner's blog on What Every C programmer should know about undefined behavior.

(a good attitude towards undefined behavior is to try hard to always avoid it; don't lose your time in trying to understand what concretely happens; but consider UB as something very dirty or "sick" that you always avoid)

To explain the observed behavior, you need to dive into the specifics of your particular implementation, notably the ABI and the calling conventions (for variadic functions à la printf). Also, look into the generated assembler code (with GCC, compile with gcc -fverbose-asm -S -O1); it might be possible that a double argument is passed in a register (or some call stack slot) different than an int argument (so the printf function is getting the garbage happening to sit in that location or register); notice also that quite often sizeof(int) could be 4 but sizeof(double) could be 8 (so the amount of data is not even right).

To avoid such mistakes, take the habit of compiling with a good compiler (such as GCC or Clang/LLVM in the free software realm) and enable all warnings and debug info (e.g. compile using gcc -Wall -Wextra -g with GCC). The compiler would have warned you.

BTW, void main() is illegal. It should be at least int main(void) and preferably int main(int argc, char**argv) and you should take care of the arguments.

With your example, gcc -Wall -Wextra (using GCC 7) tells (for your source file april.c):

april.c:4:10: warning: return type of ‘main’ is not ‘int’ [-Wmain]
     void main() {
          ^~~~
april.c: In function ‘main’:
april.c:5:14: warning: format ‘%d’ expects argument of type ‘int’,
                       but argument 2 has type ‘double’ [-Wformat=]
     printf("%d\n", 1.5);
             ~^
             %f
april.c:6:14: warning: format ‘%f’ expects argument of type ‘double’, 
                       but argument 2 has type ‘int’ [-Wformat=]
     printf("%f", 0);
             ~^
             %d

NB: Dev-C++ and CodeBlocks are not compilers, but IDEs. They both run some external compiler (perhaps GCC as MinGW on your system).

like image 20
Basile Starynkevitch Avatar answered Sep 28 '22 14:09

Basile Starynkevitch