push EAX
push 8
call malloc
pop EBX
pop EBX
mov [EAX], 0
mov [EAX+4], EBX
爲什麼我們需要做流行的EBX 2次? EBX每次獲得什麼價值?爲什麼在調用一個函數後,這段代碼會跳入同一個寄存器?
push EAX
push 8
call malloc
pop EBX
pop EBX
mov [EAX], 0
mov [EAX+4], EBX
爲什麼我們需要做流行的EBX 2次? EBX每次獲得什麼價值?爲什麼在調用一個函數後,這段代碼會跳入同一個寄存器?
基本規則是,無論你推什麼,你必須彈出。否則,你會失去平衡堆棧並導致代碼崩潰或更糟。這條規則意味着你需要彈出相同大小(以字節爲單位)的價值。
因此,在這種情況下,你推到堆棧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
您在代碼塊中的一些註釋表示每個推/彈出8個字節。 –
@peter哦,他們其實都是。我想我只是喜歡打字8.現在修好了。 –
額外的推/彈更改堆棧對齊。根據ABI的不同,'esp'可能需要在'call'之前對齊16byte(例如i386 SysV)。在帶有堆棧引擎(Pentium M及更高版本)的Intel CPU上,直接使用esp需要堆棧引擎插入額外的uop,以便將無序內核中的值與其偏移量同步。所以如果加載/存儲端口吞吐量不成問題,push/pop實際上可能是一種更便宜的方法來對齊堆棧。 clang甚至在'-O3'(不只是'-Os')上做這個,但是會彈出一個虛擬寄存器。這種風格實際上是通過push/pop進行保存/恢復,這爲EBX增加了延遲。 –
它通過移除推送的兩件東西來平衡堆棧。它不一定是'ebx',也不一定是'pop'。通常情況下你會看到'add esp,8',但這是3個字節,這只是2,所以推測可以優化大小。 – Jester