C 为什么在使用返回值时将0移动到堆栈中?
我正在试验如何分解简单C程序(用C 为什么在使用返回值时将0移动到堆栈中?,c,assembly,clang,disassembly,calling-convention,C,Assembly,Clang,Disassembly,Calling Convention,我正在试验如何分解简单C程序(用-O0编译)的clang二进制文件,我对生成的某条指令感到困惑 以下是两个带有标准参数的空main函数,其中一个返回值,另一个不返回值: // return_void.c void main(int argc, char** argv) { } // return_0.c int main(int argc, char** argv) { return 0; } 现在,当我拆卸它们的总成时,它们看起来相当不同,但有一行我不明白: return_void
-O0
编译)的clang
二进制文件,我对生成的某条指令感到困惑
以下是两个带有标准参数的空main
函数,其中一个返回值,另一个不返回值:
// 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 $0x0, -0x4(%rbp) # What does this mean?
0000000100000f8d movl %edi, -0x8(%rbp)
0000000100000f90 movq %rsi, -0x10(%rbp)
0000000100000f94 popq %rbp
0000000100000f95 retq
它只在我使用函数is not void时生成,因此我认为这可能是返回0的另一种方式,但当我更改返回的常量时,这一行根本没有更改:
// 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 $0x1, %eax # Return value modified
0000000100000f89 movl $0x0, -0x4(%rbp) # This value is not modified
0000000100000f90 movl %edi, -0x8(%rbp)
0000000100000f93 movq %rsi, -0x10(%rbp)
0000000100000f97 popq %rbp
0000000100000f98 retq
为什么会产生这一行?它的目的是什么
movl $0x0,-0x4(%rbp)
此指令将0
存储在%rbp-4
中。似乎clang为main的隐式返回值分配了一个隐藏的局部变量
从clang邮件列表中:
对。我们分配一个隐式局部变量来保存返回值;
return语句然后只初始化返回槽并跳转到
尾声,加载并返回插槽。我们不使用phi
因为到达尾声的控制流不是
由于本地的清理,必须像简单的分支一样简单
作用域(如C++析构函数)
隐式返回值(如main)由隐式存储处理
在序言中
来源:根据标准(对于托管环境),
main
需要重新输入int
结果。因此,如果违反这一点,不要期望定义的行为
此外,
main
实际上不需要显式返回0
;如果到达函数的末尾,则隐式返回。(注意这仅适用于main
,它也没有原型。clang在堆栈上为参数(寄存器edi
和rsi
)留出空间,出于某种原因,也将值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 $0, -4(%rbp)
那么,早期返回如下所示
callq rand
cmpl $42, %eax
jne .LBB0_2
movl $1, -4(%rbp)
jmp .LBB0_3
然后到了最后,它确实做到了
.LBB0_3:
movl -4(%rbp), %eax
addq $32, %rsp
popq %rbp
retq
因此,这个区域确实是为存储函数返回值而保留的。它似乎不是非常必要,也不是在优化代码中使用的,而是在
-O0
模式下使用的。您是否用普通函数(即未调用main的函数)测试过这个问题?main
没有int
返回类型是一个错误违反托管环境的标准。不要期望有任何特定行为。您禁止clang删除无用代码(使用-O0
)。你为什么要询问程序集中的奇怪代码?eax
确实有返回值,但不确定你是从哪里想到要将其存储在堆栈上的,尤其是在被调用方的框架中,调用方获得检查返回值的控件时,被调用方的框架将被破坏。你似乎是那个将其与返回地址混淆的人。Beside的事实是,无论什么系统,这都是32位ABI,而OP显然使用64位系统,返回值存储在EAX/RAX
中。您正在混淆:),也许通过名称返回地址,OP问题确实是一个很好的问题。你们都是对的,我很抱歉我犯了一个错误,我正在更新答案。感谢you@GiuseppePes:在x86平台上,随着堆栈的增长,堆栈指针向下移动(到较低的内存地址)。这意味着返回地址始终存储在函数启动时与rsp
值的正偏移量处<代码>-0x4(%rbp)在这种情况下不可能是返回地址。很高兴您正在尝试改进您的答案,但事实是它与此无关。OP清楚地知道堆栈和函数调用是如何工作的,它只是想知道(和我一样)为什么在体中有0的移动。如果你知道答案,我认为最好删除它并写一个新的:)这与问题有什么关系?@fuzzxl:在讨论生成的代码时,签名main
不符合标准与讨论该代码的问题无关?啊,是的,我错过了OP使用的伪返回类型。