2009-06-30 264 views
40

我一直在試圖深入理解編譯器如何生成機器代碼,更具體地說,GCC如何處理堆棧。在這樣做的過程中,我一直在編寫簡單的C程序,將它們編譯成彙編語言,並盡我所能瞭解結果。這裏有一個簡單的程序,並輸出它產生:堆棧分配,填充和對齊

asmtest.c

void main() { 
    char buffer[5]; 
} 

asmtest.s

pushl %ebp 
movl %esp, %ebp 
subl $24, %esp 
leave 
ret 

什麼是令人費解的我就是24個字節被分配爲堆棧。我知道,由於處理器如何處理內存,堆棧必須以4爲增量進行分配,但如果是這種情況,我們應該只將堆棧指針移動8個字節,而不是24個。作爲參考,緩衝區爲17字節產生一個移動了40個字節的堆棧指針,並且根本沒有緩衝區移動堆棧指針8.一個包含1到16個字節的緩衝區移動了12個字節。

現在假設8個字節是一個必要的常量(它需要什麼?),這意味着我們以16個字節的塊分配。爲什麼編譯器會以這種方式對齊?我使用的是x86_64處理器,但即使是64位字也只需要8字節對齊。爲何差異?

僅供參考我正在使用gcc 4.0.1運行10.5的Mac上進行編譯,並且未啓用優化。

回答

43

這是一個由-mpreferred-stack-boundary=n控制的gcc功能,編譯器會嘗試保持堆棧上的項目與2^n對齊。如果您將n更改爲2,則它只會在堆棧上分配8個字節。 n的默認值是4,即它將嘗試對齊到16字節邊界。

爲什麼有「默認」的8個字節,然後24 = 8 + 16個字節是因爲棧已經包含leaveret 8個字節,所以編譯後的代碼必須是8個字節首先調整堆得到它對齊2^4 = 16。

+0

做了「push%ebp」使esp減少了8個字節嗎?加上ret的8個字節,應該已經與16字節對齊。爲什麼劑量編譯器需要額外的8個字節? – 2013-07-12 07:52:00

+1

哦,我明白了。這是一個32位的機器。抱歉。它應該是ret 4字節+ ebp 4字節+對齊8字節+緩衝區16 – 2013-07-12 13:05:40

+1

當前版本的i386和x86-64 System V ABI需要16B堆棧對齊(在「調用」指令之前),因此函數允許假設那。歷史上,i386 ABI只需要4B對齊。 (有關ABI文檔的鏈接,請參閱https://stackoverflow.com/tags/x86/info)。即使在葉函數(不調用其他函數)時,GCC也會保持'%esp`對齊,當它必須保留任何空間時,這就是發生了什麼。 – 2017-09-07 19:31:02

3

我發現this site,它在頁面底部有一些體面的解釋,說明爲什麼堆棧可能更大。將概念擴展到64位機器,它可能解釋你所看到的。

-1

由於第一條指令將%ebp的起始值壓入堆棧(假設爲64位),因此存在8個字節。

+1

返回地址和基指針都被壓入堆棧。 – dreamlax 2009-06-30 07:12:34

11

SSEx系列指令要求打包的128位向量要對齊到16個字節 - 否則會出現段錯誤,試圖加載/存儲它們。即如果你想安全地傳遞16個字節的矢量用於堆棧中的SSE,堆棧需要一直保持對齊到16位。默認情況下,GCC佔了這個位置。

1

Mac OS X/Darwin x86 ABI需要16字節的堆棧對齊。在Linux,Win32,FreeBSD等其他x86平臺上,情況並非如此...