printf函数如何处理%f规范?

我有几个程序,其输出我无法理解:

计划1

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

输出位于http://codepad.org/ZoYsP6dc

 k is 2.000000 i is 2.000000 

计划2

再举一个例子

 #include  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; } 

这里的输出是http://codepad.org/XkZVRg64

 i is 97 a is 2.168831 a is 5.000000 

这里发生了什么? 为什么我显示输出?

您可能正在64位x86架构上运行此应用程序。 在此体系结构上,浮点参数在XMM寄存器中传递,而整数参数在通用寄存器中传递。 请参阅System V AMD64 ABI约定 。

因为%f需要浮点值:

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

打印XMM0寄存器中的值,该值恰好是先前分配的k值,而不是i在RSI寄存器中传递的值。 程序集看起来像这样:

 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 

如果您重新排序这样的分配:

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

它打印:

 i is 0.000000 k is 2.000000 

因为XMM0寄存器恰好具有值0。

[更新]它也可以在32位x86上重现。 在这个平台上, printf()基本上是将int*double*然后读取那个double 。 让我们修改示例以便于查看:

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

64位输出:

 k is 2.000000 i is 2.000000 

32位输出:

 k is 2.000000 i is -nan 

也就是说,值为-1的2 int s看起来像是double 0xffffffffffffffff,它是NaN值。

首先,对于任何可变参数函数(如printf() ,所有类型都短于int整数值都将作为int传递(或者在某些情况下在某些平台上传递unsigned int ),并且所有float值都将作为double传递。 因此你的(float)a第二次施加double

其次, printf()本身就会引导你。 如果你把垃圾送进去,就会把垃圾扔掉。 更准确地说,如果你传递一个整数,你告诉printf()期望一个doubleprintf()将尝试从参数列表中读取一个double 。 接下来发生的是未定义的行为。

一些编译器 – 尤其是GCC – 将报告文字字符串格式与相应参数列表之间的类型不匹配。 如果您的常规编译器不进行该分析,请考虑使用一个编译器 – 至少对于初步编译来解决编译错误。

编译器不会根据%f等格式说明符进行任何类型转换。 每个参数都以正常方式传递,并且给出与其格式说明符不匹配的参数是程序中的错误,导致未定义的行为。

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

这里没什么奇怪的。 字符a是ASCII中的数字97。

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

a是一个值为5的int。在内存中,这将是字节[0x5 0x0 0x0 0x0] 。 但是这个命令取决于printf。 它说“只要相信我指出浮动”。 printf相信你。 浮点数非常奇怪,但基本上它们的工作方式类似于科学记数法但在基数2中而不是基数10.通过随机机会,将2.1688指定为浮点数的方式恰好是[0x5 0x0 0x0 0x0] 。 这就是printf告诉你的。

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

在这里你告诉编译器你想在printf看到它之前将其转换为浮点数。 C编译器知道如何在奇怪的浮点格式中改变表达式5。 所以printf得到它所期望的,你看到5.0000

后续酷实验:想看到别的东西整洁吗? 尝试

 union data { int i; float f; char ch[4]; }; union data d; di = 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]); df = 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 

您实际上可以看到C编译器如何为您更改数据(假设这有效…)

它更有趣。 我在三台不同的linux机器上测试了它,并为第二条输出线得到了两个不同的结果:

 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