我有以下C代码:
#include <stdio.h>
int function(int a, int b)
{
int res = a + b;
return res;
}
int main(){
function(1,2);
exit(0);
}
我使用 gcc 4.8.2 (在Ubuntu 14下)为 x86-64 编译它并生成此代码:
000000000040052d <function>:
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: 89 7d ec mov %edi,-0x14(%rbp)
400534: 89 75 e8 mov %esi,-0x18(%rbp)
400537: 8b 45 e8 mov -0x18(%rbp),%eax
40053a: 8b 55 ec mov -0x14(%rbp),%edx
40053d: 01 d0 add %edx,%eax
40053f: 89 45 fc mov %eax,-0x4(%rbp)
400542: 8b 45 fc mov -0x4(%rbp),%eax
400545: 5d pop %rbp
400546: c3 retq
我无法理解某些事情。
一开始我们推送 rbp 并将 rsp 保存在 rbp 中。然后在顶部 然后堆叠(并且在%rbp )我们已经保存了rbp。然后 rbp 下面的所有内容都是可用空间。
但是我们将 edi 和 esi 的传递参数放在 -0x14(%rbp)及以下。
但是为什么我们不能将它们放在rbp / rsp指向的位置之下? edi 和 esi < / strong>是4个字节长,为什么不-0x8(%rbp)和-0xc(%rbp),那么?它与内存对齐有关吗?
为什么在返回 之前有一个奇怪的保存eax来堆叠并回读它?
答案 0 :(得分:3)
首先,请注意您正在查看未经优化的编译器输出。编译器输出通常最终看起来有点愚蠢,优化已关闭,因为编译器会将C的每一行字面转换为等效的程序集,而不必费心去做最简单,最明显的优化。
对于您的第一个问题,答案是“因为这是您的编译器决定变量应该去的地方”。没有更好的答案 - 编译器的堆栈分配方案差异很大。例如,我机器上的Clang输出了这个:
pushq %rbp
movq %rsp, %rbp
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -4(%rbp), %esi
addl -8(%rbp), %esi
movl %esi, -12(%rbp)
movl -12(%rbp), %eax
popq %rbp
retq
您可以清楚地看到a
存储在-4,b
存储在-8,result
存储在-12。这比你的GCC给你的包装更严格,但这只是GCC的一个怪癖,仅此而已。
关于第二个问题,让我们看看说明如何映射到C:
标准功能序言(设置堆栈帧):
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
将两个参数存储到堆栈变量a
和b
中:
400531: 89 7d ec mov %edi,-0x14(%rbp)
400534: 89 75 e8 mov %esi,-0x18(%rbp)
为a
b
和a + b
400537: 8b 45 e8 mov -0x18(%rbp),%eax
40053a: 8b 55 ec mov -0x14(%rbp),%edx
实际上做a + b
40053d: 01 d0 add %edx,%eax
设置result = (result of a+b)
40053f: 89 45 fc mov %eax,-0x4(%rbp)
将result
复制到返回值(return result;
)
400542: 8b 45 fc mov -0x4(%rbp),%eax
实际上回归:
400545: 5d pop %rbp
400546: c3 retq
因此,您可以看到eax
的冗余保存和加载仅仅是因为保存和加载对应于原始C文件的不同语句:保存来自result =
且负载来自return result;
。
为了比较,这里是Clang的优化输出(-O
):
pushq %rbp
movq %rsp, %rbp
addl %esi, %edi
movl %edi, %eax
popq %rbp
retq
更聪明:没有堆栈操作,整个函数体只是两个指令addl
和movl
。 (当然,如果你声明函数static
,那么GCC和Clang都会很高兴地发现该函数从未被有效地使用过,只需将其彻底删除即可。)。