5

I tried to print float using %d (I know that this shouldn't be done. But every time re-run executable it gives out a different value)
My question is : Why does the printed value changes every time ? My System : Ubuntu 14.04 (64 bit) Compiler : 4.8.4 Here is the code:

#include<stdio.h>

int main(){
  float b = 12.3456;

  printf("%d\n",b);

}

Sample output:

4bh1@mybox:~/C-fi$ ./test 
-1629995944
4bh1@mybox:~/C-fi$ ./test 
1147348376
4bh1@mybox:~/C-fi$ ./test 
-1746005432
4bh1@mybox:~/C-fi$ ./test 
510102216
4bh1@mybox:~/C-fi$ 
4bh1
  • 182
  • 2
  • 13

3 Answers3

8

Using the wrong format specifier for printf is a classic example of undefined behavior. That means the behavior is unpredictable and can't be depended on to be consistent from one run to the next. It could crash, it could print random values, or it could appear to work.

What's actually happening is an implementation detail of the compiler and architecture in question. But unless you're actually writing a compiler, there's not much use in digging into it.

If you're familiar with assembler, you could look at the compiled code to see what instructions it generated. Keep in mind however that different compilers (even the same compiler with different optimization settings) will most likely generate different assembly.

dbush
  • 205,898
  • 23
  • 218
  • 273
7

Probably floating-point value is passed via FPU register, but printf() tries to read integer from somewhere else, where it expects to see integer (stack or another register). And content of that place is unspecified. Strictly saying (as @dbush has mentioned) your code causes undefined behavior.

Sergio
  • 8,099
  • 2
  • 26
  • 52
  • That would not correspond to any ABI I know, but some ABIs pass floating-point values to be printed with `%f` and integer values to be printed with `%d` both in registers, which could possibly explain what we see depending on preceding code (and ASLR). – Pascal Cuoq Jul 08 '16 at 12:07
  • 1
    e.g. for x64, https://msdn.microsoft.com/en-us/library/ms235286.aspx says "The arguments are passed in registers RCX, RDX, R8, and R9. If the arguments are float/double, they are passed in XMM0L, XMM1L, XMM2L, and XMM3L." So printf is expecting a float in XMM0L but that register never got set by your code. – moonshadow Jul 08 '16 at 12:10
  • 1
    @PascalCuoq The SysV ABI for amd64 is like this. – fuz Jul 08 '16 at 12:17
  • @FUZxxl The integer in this case will be passed in a register. The first 6 integers are passed in registers, 7th and up are on the stack. – Art Jul 08 '16 at 12:34
  • @PascalCuoq Thanks for remind about register-passed args. Correcting answer. – Sergio Jul 08 '16 at 12:51
  • @Art Yes, but integers and floating point arguments are passed in different registers. If the second argument to `printf` is an integer it goes into `rsi`, if it's a double it goes into `xmm0`. – fuz Jul 08 '16 at 13:46
1

As indicated in the comments, this sounds like undefined behaviour, which explains (well, sort of) what's happening.

In case you want to read the internal representation of a float, or you want to do some low-level hacks on the number, there's simple ways to do so, for example:

union {
    float f;
    int i;
} n;

n.f = 12.3456;
printf("%d\n", n.i); // should be the same number each time

Note that this only works if float and int have the same size on your system, but that should be the case for most people. You could add an assert(sizeof(float) == sizeof(int)) if you want to be sure.

Joris
  • 412
  • 3
  • 8
  • Strictly saying this `union` hack is non-portable at least because fields may not overlap due different alignment rules. It would be better to do it with `memcpy()` . – Sergio Jul 08 '16 at 12:14
  • 1
    @Serhio There is never any padding at the start of a `union`, so why wouldn't the members overlap? ISO/IEC 9899:2011 § 6.7.2.1 (15) `…There may be unnamed padding within a structure object, but not at its beginning.` Admittedly, that neglects to mention union objects though. – Ian Abbott Jul 08 '16 at 16:33
  • Further to my previous comment, §6.5.3.4 (4) about `sizeof`: `… When applied to an operand that has structure or union type, the result is the total number of bytes in such an object, including internal and trailing padding.` It does not mention leading padding, which suggests it doesn't exist. – Ian Abbott Jul 08 '16 at 16:44