将printf移动到不同的行会产生不同的输出? (C)

时间:2015-11-13 12:03:12

标签: c casting int printf

在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。 有什么想法吗?

2 个答案:

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

简短的回答是前几个论点是通过寄存器传达的。选择取决于参数的类型:整数参数进入“经典”寄存器(ediesi等),浮点进入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(获取指向格式字符串的指针)和ediesi ,由于整数除法,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