2016-07-07 74 views

回答

4

基本規則是,無論你推什麼,你必須彈出。否則,你會失去平衡堆棧並導致代碼崩潰或更糟。這條規則意味着你需要彈出相同大小(以字節爲單位)的價值。

因此,在這種情況下,你推到堆棧8個字節調用malloc之前:

push EAX ; push a DWORD-sized register (4 bytes) 
push 8  ; push a DWORD immediate  (4 bytes) 

到函數調用(由malloc後需清理堆棧,因爲它使用了cdecl調用約定,這是調用者清理),你需要彈出8個字節。它只是恰巧,一個方便的方式來做到這一點是彈出一個寄存器大小的值的兩倍:

pop EBX ; pop 4 bytes 
pop EBX ; pop 4 bytes 

堆棧是LIFO。 第一個pop將8放入EBX(因爲這是你推送的最後一個東西),你不關心它。下一次彈出將原始值EAX放回到EBX(您推送的第一件東西)中,然後稍後繼續使用。

如果你不關心保留任何值被彈出堆棧,你可以簡單地使用ADD指令,將8個字節的堆棧指針:

add esp, 8 

這可能是輕微比兩個pop更快,但它實際上稍大一點(3個字節,而不是2個字節,as Jester points out)有時,代碼大小的優化與優化代碼速度一樣重要,因爲當代碼更小時,更多代碼可以適合緩存。但在這種情況下,我懷疑更重要的問題是獲得推動的第一個價值。由於malloc只接受一個參數,第一次推動的唯一原因是保留原始值EAX,因爲它被函數調用破壞(函數返回它們的結果EAX)。因此,編寫代碼的替代方式應該是:

; Save EAX by moving it into a caller-save register 
; (that will not get clobbered by the malloc function). 
mov EBX, EAX 

; Call the malloc function by pushing a 4-byte parameter and then rebalancing the stack. 
push 8 
call malloc 
add esp, 4 

; EAX contains malloc's return value, and EBX contains the original value of EAX 
; that we saved before calling malloc. 
mov [EAX], 0 
mov [EAX+4], EBX 
+0

您在代碼塊中的一些註釋表示每個推/彈出8個字節。 –

+0

@peter哦,他們其實都是。我想我只是喜歡打字8.現在修好了。 –

+1

額外的推/彈更改堆棧對齊。根據ABI的不同,'esp'可能需要在'call'之前對齊16byte(例如i386 SysV)。在帶有堆棧引擎(Pentium M及更高版本)的Intel CPU上,直接使用esp需要堆棧引擎插入額外的uop,以便將無序內核中的值與其偏移量同步。所以如果加載/存儲端口吞吐量不成問題,push/pop實際上可能是一種更便宜的方法來對齊堆棧。 clang甚至在'-O3'(不只是'-Os')上做這個,但是會彈出一個虛擬寄存器。這種風格實際上是通過push/pop進行保存/恢復,這爲EBX增加了延遲。 –

相關問題