2015-06-25 54 views
0

我有下面的C代码:x86_64的ABI:拆卸问题

#include <stdio.h> 

int function(int a, int b) 
{ 
    int res = a + b; 
    return res; 
} 

int main(){ 
    function(1,2); 
    exit(0); 
} 

我编译它为x86-64的GCC 4.8.2(Ubuntu的14下),它产生的代码:

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和保存RSPRBP。然后在 的顶部堆叠(并在%rbp),我们已经保存了rbp。然后,所有低于rbp是可用空间。

但后来我们把传入的参数从EDIESI-0x14(%RBP)及以下。

但是为什么我们不能把它们直接放在rbp/rsp指向的地方?edi and esi是4个字节长,为什么不是-0x8(%rbp)和-0xc(%rbp)呢?它是否与内存对齐连接?

为什么有一个奇怪的储蓄EAX堆栈和返回之前回读?

回答

3

首先,请注意您正在寻找未优化的编译器输出。编译器输出通常最终会因为优化被关闭而变得愚蠢,因为编译器会逐字地将C的每一行转换为等效的程序集运行,而无需进行最简单,最明显的优化。

对于第一个问题,答案是“因为这是编译器决定变量应该去的地方”。没有更好的答案 - 编译器的堆栈分配方案差异很大。例如,锵我的机器上输出这个:

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给你的包装更紧密,但这只是海湾合作委员会的一个怪癖而已,除此之外别无其他。

关于第二个问题,我们只是看说明书如何映射到C:


标准函数序言(设置栈帧):

40052d:  55      push %rbp 
    40052e:  48 89 e5    mov %rsp,%rbp 

商店两个参数进堆栈变量ab

400531:  89 7d ec    mov %edi,-0x14(%rbp) 
    400534:  89 75 e8    mov %esi,-0x18(%rbp) 

负载aba + 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;

为了便于比较,这里是锵的优化输出(-O):

pushq %rbp 
movq %rsp, %rbp 
addl %esi, %edi 
movl %edi, %eax 
popq %rbp 
retq 

聪明得多:没有堆栈操作,整个函数体只是两个指令addlmovl。 (当然,如果你声明函数static,那么GCC和Clang都会高兴地检测到这个函数从未被有效使用,并且直接删除它。)。