在C中,当我将此printf行:printf("%f\n", 5 / 2);
移动到不同的行时,其输出会发生变化。有什么想法吗?
下面是代码:
#include <stdlib.h>
#include <stdio.h>
int main()
{
int a = 65;
char c = (char)a;
int m = 3.0/2;
printf("%c\n", c);
printf("%f\n", (float)a);
printf("%f\n", 5.0 / 2);
printf("%f\n", 5 / 2.0);
printf("%f\n", (float)5 / 2);
printf("%f\n", 5 / (float)2);
printf("%f\n", (float)(5 / 2));
printf("%f\n", 5.0 / 2);
printf("%d\n", m);
printf("%f\n", 5 / 2);
system("PAUSE");
return(0);
}
继续输出:
A
65.000000
2.500000
2.500000
2.500000
2.500000
2.000000
2.500000
1
2.500000
如果我将printf("%f\n", 5 / 2);
移动到第一行之一(输出A的那一行和输出65.000000的那一行之间),它将打印0.000000(这是有道理的)而不是现在的2.500000。
有什么想法吗?
答案 0 :(得分:4)
您的代码正在调用未定义的行为。
您有义务使用正确的数据说明符在printf
中打印内容,如果不这样做则调用UB。因此,在不同的地方获得不同的结果并不重要也就不足为奇了。
http://en.cppreference.com/w/c/io/fprintf
如果转换规范无效,则行为未定义。
同样适用于c。
当调用未定义的行为时,结果按照定义是随机的而不是可预测的,因此要求我们预测它们是没有意义的。
答案 1 :(得分:3)
正如评论者指出的那样,行printf("%f\n", 5 / 2);
只表现出未定义的行为。但是,让我们看看为什么你可以使用System V ABI在x86-64架构上获得这样的结果。
简短的回答是前几个论点是通过寄存器传达的。选择取决于参数的类型:整数参数进入“经典”寄存器(edi
,esi
等),浮点进入SSE寄存器(xmm0
,{{ 1}}等。)。
因为我们在格式字符串中给出了错误的类型,xmm1
正在从错误的寄存器中读取参数。
让我们将您的计划简化为以下内容:
printf
现在让我们来看看#include <stdio.h>
int main(void)
{
printf("%f\n", 5/2);
printf("%f\n", 5.0/2);
printf("%f\n", 5/2);
return 0;
}
的反汇编。我们从功能序言开始,这不是太特别:
main
然后,我们第一次调用 push %rbp
mov %rsp,%rbp
sub $0x10,%rsp
,其中参数传递给printf
(获取指向格式字符串的指针)和edi
(esi
,由于整数除法,5/2
:
2
但是, mov $0x2,%esi
mov $0x4005e4,%edi
mov $0x0,%eax
callq 4003e0 <printf@plt>
会读取printf
格式并尝试从"%f\n"
读取参数。就我而言,此寄存器的值为xmm0
,因此打印出0
。
在第二次调用中,参数显然是一个浮点数,它通过0.000000
传递:
xmm0
现在, movabs $0x4004000000000000,%rax
mov %rax,-0x8(%rbp)
movsd -0x8(%rbp),%xmm0
mov $0x4005e4,%edi
mov $0x1,%eax
callq 4003e0 <printf@plt>
打印出预期的printf
(您在此处看到2.500000
,这就是2.5的64位浮点常量)。我们将其传递给0x4004000000000000
,并从xmm0
读取。
第三个电话与第一个电话完全相同:
xmm0
更改的内容是对 mov $0x2,%esi
mov $0x4005e4,%edi
mov $0x0,%eax
callq 4003e0 <printf@plt>
的调用未更改printf
中的值。它仍然包含第二次调用之前的常量2.5,因为我们第三次调用xmm0
。在第三次通话中,printf
将再次打印printf
。
(我们的功能以枯燥的2.500000
结束,当然:)
return 0