How printf function handle %f specification?

2020-05-07 02:12发布

I have a couple of programs whose output I cannot understand:

Program 1

#include <stdio.h>
int main(void)
{
  int i=1;
  float k=2;
  printf("k is %f \n",k);
  printf("i is %f \n",i);
  return 0;
}

Output is at http://codepad.org/ZoYsP6dc

k is 2.000000 
i is 2.000000

Program 2

Now one more example

#include <stdio.h>

int main(void)
{
  char i='a';
  int a=5;
  printf("i is %d \n",i);  // here %d type cast char value in int
  printf("a is %f \n",a);  // hete %f dont typecast float value
  printf("a is %f \n",(float)a);  // if we write (float) with %f then it works
  return 0;
}

Here output is at http://codepad.org/XkZVRg64

i is 97 
a is 2.168831 
a is 5.000000 

Question

What is going on here? Why am I getting the outputs shown?

5条回答
戒情不戒烟
2楼-- · 2020-05-07 02:15

You are probably running this application on a 64-bit x86 architecture. On this architecture floating point arguments are passed in XMM registers, whereas integer arguments get passed in the general purpose registers. See System V AMD64 ABI convention.

Because %f expects a floating point value:

printf("i is %f \n",i);

prints the value from XMM0 register, which happens to be the value of k assigned earlier and not i passed in RSI register. Assembly looks like this:

movl    $.LC1, %edi       # "k is %f \n"
movsd   .LC0(%rip), %xmm0 # float k = 2
call    printf
movl    $1, %esi          # int i = 1
movl    $.LC2, %edi       # "i is %f \n"
call    printf            # prints xmm0 because of %f, not esi

If you reorder the assignments like this:

int i = 1;
printf("i is %f \n",i);

float k = 2;
printf("k is %f \n",k);

It prints:

i is 0.000000 
k is 2.000000 

Because XMM0 register happens to have value of 0.

[Update] It is reproducible on a 32-bit x86 as well. On this platform printf() is basically casting int* to double* and then reading that double. Let's modify the example to make it easy to see:

int main() {
    float k = 2;
    int i = -1;
    printf("k is %f \n",k);
    printf("i is %f \n",i,i);
}

64-bit output:

k is 2.000000 
i is 2.000000 

32-bit output:

k is 2.000000 
i is -nan 

That is, 2 ints with value of -1 look like a double 0xffffffffffffffff which is a NaN value.

查看更多
欢心
3楼-- · 2020-05-07 02:20
printf("i is %d \n",i); 

Nothing weird here. The character a is number 97 in ASCII.

printf("a is %f \n",a);

a is an int with the value 5. In memory, that'll be the bytes [0x5 0x0 0x0 0x0]. This command lies to printf, though. It says "just trust me that a points to a float". printf believes you. Floats are pretty weird, but basically they work like scientific notation but in base 2 instead of base 10. By random chance, the way that you specify 2.1688 as a float happens to be [0x5 0x0 0x0 0x0]. So that's what printf shows you.

printf("a is %f \n",(float)a);

Here you've told the compiler you want to convert a into a float before printf sees it. The C compiler knows how to change things to express 5 in the weird float format. So printf gets what it expects, and you see 5.0000

Follow-up cool experiment: Want to see something else neat? Try

union data {
    int i;
    float f;
    char ch[4];
};
union data d;
d.i = 5;
printf("How 5 is represented in memory as an integer:\n");
printf("0x%X 0x%X 0x%X 0x%X\n", v.ch[0], v.ch[1], v.ch[2], v.ch[3]);
d.f = 5.0;
printf("How 5 is represented in memory as a float:\n");
printf("0x%X 0x%X 0x%X 0x%X\n", v.ch[0], v.ch[1], v.ch[2], v.ch[3]);

// OUTPUT:
// How 5 is represented in memory as an integer:
// 0x5 0x0 0x0 0x0
// How 5 is represented in memory as a float:
// 0x0 0x0 0xFFFFFFA0 0x40

You can actually see how the C compiler changes the data around for you (assuming this works...)

查看更多
三岁会撩人
4楼-- · 2020-05-07 02:22

It is even more interesting. I tested it on three different linux machines and got two different results for second output line:

gcc 4.5.1     32bit: a is -0.000000
gcc 4.5.1 -Ox 32bit: a is 0.000000
gcc 4.5.1     64bit: a is 0.000000
gcc 4.6.2     64bit: a is 0.000000 
查看更多
▲ chillily
5楼-- · 2020-05-07 02:30

The compiler does not do any type conversions based on format specifiers like %f. Each parameter is passed in the normal manner, and giving an argument that does not match its format specifier is a bug in your program resulting in undefined behaviour.

查看更多
一纸荒年 Trace。
6楼-- · 2020-05-07 02:40

First, with any variadic function such as printf(), all integer values of a type shorter than int are passed as int (or unsigned int in some cases on some platforms), and all float values are passed as double. Your (float)a therefore is cast a second time to double.

Secondly, printf() itself takes you at your word. If you pass garbage in, you get garbage out. More accurately, if you pass an integer where you tell printf() to expect a double, printf() will try to read a double from the parameter list. What happens next is undefined behaviour.

Some compilers - notably GCC - will report type mismatches between literal string formats and the corresponding parameter list. If your regular compiler does not do that analysis, consider using a compiler that will - at least for preliminary compilations to sort out the compilation errors.

查看更多
登录 后发表回答