将printf移动到不同的行会产生不同的输出? (C)
在C中,当我移动这个printf行时: printf("%f\n", 5 / 2);
它的输出变化到不同的行。 有任何想法吗?
inheritance人代码:
#include #include 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(这是有意义的)而不是现在的25000000。 有任何想法吗?
您的代码正在调用未定义的行为。
您有义务使用正确的数据说明符在printf
打印内容,如果不这样做则调用UB。 因此,在不同的地方获得不同的结果并不重要也就不足为奇了。
http://en.cppreference.com/w/c/io/fprintf
如果转换规范无效,则行为未定义。
c也是如此。
在调用未定义的行为时,结果是按照定义随机且不可预测的,因此要求我们预测它们毫无意义。
正如评论者指出的那样,行printf("%f\n", 5 / 2);
只是表现出未定义的行为。 但是,让我们看看为什么你可以使用System V ABI在x86-64架构上获得这样的结果。
简短的回答是前几个参数是通过寄存器传达的。 选择取决于参数的类型:整数参数进入“经典”寄存器( edi
, esi
等),浮点进入SSE寄存器( xmm0
, xmm1
等)。
因为我们在格式字符串中给出了错误的类型,所以printf
正在从错误的寄存器中读取参数。
让我们将您的程序简化为以下内容:
#include int main(void) { printf("%f\n", 5/2); printf("%f\n", 5.0/2); printf("%f\n", 5/2); return 0; }
现在让我们来看看main
的反汇编。 我们从function序言开始,这不是太特别:
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
将读取"%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
打印出预期的2.500000
(你在这里看到的是0x4004000000000000
,这就是2.5的64位浮点常量)。 我们将它传递给xmm0
,它从xmm0
读取它。
第三个电话与第一个电话完全相同:
mov $0x2,%esi mov $0x4005e4,%edi mov $0x0,%eax callq 4003e0
更改的是对printf
的调用没有改变xmm0
的值。 它仍然包含第二次调用之前的常量2.5,因为我们第三次调用printf
。 在第三次调用中, printf
将再次打印2.500000
。
(我们的function以无聊的return 0
结束,当然:)
mov $0x0,%eax leaveq retq