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

Spa*_*ato 5 c int printf casting

在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);
}
Run Code Online (Sandbox Code Playgroud)

继承人的产量:

A
65.000000
2.500000
2.500000
2.500000
2.500000
2.000000
2.500000
1
2.500000
Run Code Online (Sandbox Code Playgroud)

如果我移动printf("%f\n", 5 / 2);到第一行之一(在输出A的那一行和输出65.000000的那一行之间),它将打印0.000000(这是有意义的)而不是现在的2.500000.有任何想法吗?

小智 3

正如评论者所指出的,这条线printf("%f\n", 5 / 2);只是表现出未定义的行为。但让我们看看为什么您可能会在使用 System V ABI 的 x86-64 架构上得到这样的结果。

简而言之,前几个参数是通过寄存器进行通信的。选择取决于参数的类型:整数参数进入“经典”寄存器(ediesi等),浮点参数进入 SSE 寄存器(xmm0xmm1等)。

因为我们在格式字符串中给出了错误的类型,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;
}
Run Code Online (Sandbox Code Playgroud)

现在让我们来看看 的反汇编main。我们从函数序言开始,它并不太特别:

   push   %rbp
   mov    %rsp,%rbp
   sub    $0x10,%rsp
Run Code Online (Sandbox Code Playgroud)

然后,我们第一次调用printf,参数被传递到edi(它获取指向格式字符串的指针)和esi( 5/2,这是2由于整数除法):

   mov    $0x2,%esi
   mov    $0x4005e4,%edi
   mov    $0x0,%eax
   callq  4003e0 <printf@plt>
Run Code Online (Sandbox Code Playgroud)

但是,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>
Run Code Online (Sandbox Code Playgroud)

现在,printf打印出预期的结果2.500000(您在此处看到的是0x4004000000000000,这就是 2.5 的 64 位浮点常量的样子)。我们传递它xmm0,它从 读取它xmm0

第三次调用与第一个调用完全相同:

   mov    $0x2,%esi
   mov    $0x4005e4,%edi
   mov    $0x0,%eax
   callq  4003e0 <printf@plt>
Run Code Online (Sandbox Code Playgroud)

发生的变化是对 的调用printf没有改变 中的值xmm0。当我们第三次调用时,它仍然包含第二次调用之前的常量 2.5 printf。在第三次调用时,printf2.500000再次打印。

(当然,我们的函数以无聊的结束return 0:)

   mov    $0x0,%eax
   leaveq 
   retq   
Run Code Online (Sandbox Code Playgroud)