2015-06-25 33 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都會高興地檢測到這個函數從未被有效使用,並且直接刪除它。)。