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()
期望一个double
, printf()
将尝试从参数列表中读取一个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