为什么在使用 return 值时将 0 移动到堆栈?

Why is 0 moved to stack when using return value?

我正在尝试反汇编 clang 简单 C 程序的二进制文件(用 -O0 编译),我对生成的某条指令感到困惑。

这里有两个带有标准参数的空 main 函数,其中一个 return 的值而另一个没有:

// return_void.c
void main(int argc, char** argv)
{
}

// return_0.c
int main(int argc, char** argv)
{
    return 0;
}

现在,当我拆解它们的组件时,它们看起来相当不同,但有一行我不明白:

return_void.bin:
(__TEXT,__text) section
_main:
0000000000000000    pushq   %rbp
0000000000000001    movq    %rsp, %rbp
0000000000000004    movl    %edi, -0x4(%rbp)
0000000000000007    movq    %rsi, -0x10(%rbp)
000000000000000b    popq    %rbp
000000000000000c    retq

return_0.bin:
(__TEXT,__text) section
_main:
0000000100000f80    pushq   %rbp                
0000000100000f81    movq    %rsp, %rbp          
0000000100000f84    xorl    %eax, %eax          # We return with EAX, so we clean it to return 0
0000000100000f86    movl    [=12=]x0, -0x4(%rbp)    # What does this mean?
0000000100000f8d    movl    %edi, -0x8(%rbp)
0000000100000f90    movq    %rsi, -0x10(%rbp)
0000000100000f94    popq    %rbp
0000000100000f95    retq

它只有在我使用函数时才会生成 not void,所以我认为这可能是 return 0 的另一种方式,但是当我更改 returned 常量时,这一行完全没有改变:

// return_1.c
int main(int argc, char** argv)
{
    return 1;
}

empty_return_1.bin:
(__TEXT,__text) section
_main:
0000000100000f80    pushq   %rbp
0000000100000f81    movq    %rsp, %rbp
0000000100000f84    movl    [=13=]x1, %eax           # Return value modified
0000000100000f89    movl    [=13=]x0, -0x4(%rbp)    # This value is not modified
0000000100000f90    movl    %edi, -0x8(%rbp)
0000000100000f93    movq    %rsi, -0x10(%rbp)
0000000100000f97    popq    %rbp
0000000100000f98    retq

为什么会生成这条线,它的用途是什么?

movl   [=10=]x0,-0x4(%rbp)

该指令将0存储在%rbp - 4。 clang 似乎为来自 main.

的隐式 return 值分配了一个隐藏的局部变量

来自 clang 邮件列表:

Yes. We allocate an implicit local variable to hold the return value; return statements then just initialize the return slot and jump to the epilogue, where the slot is loaded and returned. We don't use a phi because the control flow for getting to the epilogue is not necessarily as simple as a simple branch, due to cleanups in local scopes (like C++ destructors).

Implicit return values like main's are handled with an implicit store in the prologue.

来源:http://lists.cs.uiuc.edu/pipermail/cfe-dev/2012-February/019767.html

根据标准(对于托管环境),需要 5.1.2.2.1main 才能返回 int 结果。因此,如果违反此规定,不要指望定义的行为。

此外,main 实际上_不需要显式 return 0;如果到达函数末尾,则隐式 returned。 (请注意,这仅适用于 main,它也没有原型。

clang 正在为参数(寄存器 edirsi)在堆栈上创建 space 并且出于某种原因也将值 0 放在堆栈上。我假设 clang 将您的代码编译为这样的 SSA 表示形式:

int main(int argc, char** argv)
{
    int a;

    a = 0;
    return a;
}

这可以解释为什么要分配堆栈槽。如果 clang 也进行持续传播,这就可以解释为什么 eax 被清零而不是从 -4(%rbp) 加载。一般来说,不要过多考虑 未优化 程序集中的可疑构造。毕竟,您禁止编译器删除无用代码。

该区域的用途由以下代码揭示

int main(int argc, char** argv)
{
    if (rand() == 42)
      return 1;

    printf("Helo World!\n");
    return 0;
}

一开始是

movl    [=11=], -4(%rbp)

然后早期的return看起来如下

callq   rand
cmpl    , %eax
jne .LBB0_2
movl    , -4(%rbp)
jmp .LBB0_3

然后在最后

.LBB0_3:
movl    -4(%rbp), %eax
addq    , %rsp
popq    %rbp
retq

所以,这个区域确实是预留的,用来存放函数return的值。它似乎不是非常必要,也没有在优化代码中使用,但在 -O0 模式下它就是这样工作的。