printf函数如何在C中工作?
我在测试函数printf时遇到了一个问题:
首先我写这样的代码:
int main(void) { char a = 'a'; printf("a = %f\n", a); return 0; }
输出是
然后我写代码:
int main(void) { float b = 'a'; printf("b = %f\n", b); return 0; }
输出是
然后我写代码:
int main(void) { char a = 'a'; float b = 'a'; printf("b = %f\n", b); printf("a = %f\n", a); return 0; }
输出是
所以我很困惑为什么在第一个程序中a
= 0.000000
而在第三个程序中a
= 97.000000
?
函数printf()
如何工作?
符号%f
, %d
工作?
更新:在对此进行更多研究后,似乎float
和内存表示之间的差异不是负责三个程序行为的表示。
我查看了第三个程序的目标代码,我发现了奇怪行为的原因:浮点数参数被发送到其他注册表/堆栈而不是整数。 并且printf
依赖于它并在其他位置查找它们,而不是printf
的被调用者(即main
方法)放置参数。
这是第三个程序的相关反汇编(对于x86_64架构):
0000000100000f18 leaq 0x71(%rip), %rdi ## literal pool for: "b = %f\n" 0000000100000f1f movsd 0x61(%rip), %xmm0 ## b variable gets sent to xmm0 0000000100000f27 movl $0x0, -0x4(%rbp) 0000000100000f2e movb $0x61, -0x5(%rbp) ## a variable gets placed on the callee stack 0000000100000f32 movsd %xmm0, -0x10(%rbp) 0000000100000f37 movsd -0x10(%rbp), %xmm0 0000000100000f3c movb $0x1, %al 0000000100000f3e callq 0x100000f66 ## symbol stub for: _printf 0000000100000f43 leaq 0x4e(%rip), %rdi ## literal pool for: "a = %f\n" 0000000100000f4a movsbl -0x5(%rbp), %esi 0000000100000f4e movl %eax, -0x14(%rbp) 0000000100000f51 movb $0x0, %al 0000000100000f53 callq 0x100000f66 ## symbol stub for: _printf
并且printf
依赖于此,它假定被调用者在xmm0
/ xmm1
/ etc寄存器中放置了%f
参数,并且这三个程序的行为如下所示:
- 在第一个程序中,
printf
在xmm0寄存器中查找%f
参数,但是当我们在程序的开头时,寄存器是干净的并且main
已经放入a
eax
,因此xmm0
保持零值,这是什么printf
打印 - 在第二个程序
main
正确地将b
放在xmm0
,printf
从那里取出,打印正确的值 - 在第三个程序中,由于首先打印
b
,xmm0
寄存器将保持该值,并且因为printf
没有弄乱寄存器,当它第二次被调用时,它再次从xmm0
取出,在第一次之后保持完整printf
电话。
所以关于调用者和被调用者的约定都是关于调用者发送整数和浮点数以及被调用者尝试拾取它们的地方。
原始响应:在第一个程序中,您尝试打印浮点数,但是传递一个int(char是一个较小的int)。 由于int和浮点数具有不同的二进制表示,因此int 97(对应于字符’a’)对应于非常小的浮点数:1.36E-43,其打印为零。
这是97的二进制表示(编译器在调用函数时将任何1字节的char扩展为4字节的参数)
00000000 00000000 00000000 01100001
IEEE 754是浮点/双数的二进制表示的标准格式,您可以在这里使用在线转换器,并且当它被解释为int或float时,您可以看到相同的二进制数具有不同的值。
这里的%f
代表浮点数的替换标记。
为了替换char,你需要%c
。
这是一个列表 ,告诉您每种类型的适当替换标记是什么。
%f表示字符的Float%c
The 97 which you have got is the ASCII value for 'a'
根据最新的’C’标准,它是一种未定义的行为。 检查c标准草案的7.21.6.1第9页。
如果转换规范无效,则行为未定义.282)如果任何参数不是相应转换规范的正确类型,则行为未定义。
因此,当事物被认为具有未定义的行为时,任何事情都是可能的,并且行为可能因编译器而异。 ‘C’让你用斧头剪你的脚趾,但这不是我应该做的。
%f
适用于浮动。 您必须使用%c
作为字符。
如果你使用
printf("a = %c\n", a);
你会得到这个角色。
因此,如果您将第一个代码更改为
int main(void) { char a = 'a'; printf("a = %c\n", a); return 0; }
你会得到输出
a
之间的区别
printf("%f\n, 97.0);
和
printf("%c\n, 'a');
是printf函数根据你给出的%X
从堆栈中读取它的参数,并解释它们(用于显示)。
对于%c
printf需要char作为参数,因此它将读取一个char(一个字节,但实际上通常是一个int,它依赖于实现)并显示它(如果提供了int,它会显示不太重要的字节)。
对于%f
printf,需要一个float(以字节为单位的sizeof(float)
,通常是gcc / Intel处理器上的4个字节)。
如果使用gcc进行编译,请使用-Wall
选项,该选项会在%X
格式和参数类型不匹配时发出警告。