将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架构上获得这样的结果。

简短的回答是前几个参数是通过寄存器传达的。 选择取决于参数的类型:整数参数进入“经典”寄存器( ediesi等),浮点进入SSE寄存器( xmm0xmm1等)。

因为我们在格式字符串中给出了错误的类型,所以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 (获取指向格式字符串的指针)和esi5/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