调用函数后,为什么这段代码会弹出两次相同的寄存器?

时间:2016-07-07 13:20:24

标签: assembly x86

push EAX
push 8
call malloc
pop EBX
pop EBX
mov [EAX], 0
mov [EAX+4], EBX

为什么我们需要做2次pop EBX?每次EBX会获得什么价值?

1 个答案:

答案 0 :(得分:4)

基本规则是,无论你推动什么,都必须弹出。否则,您将使堆栈失衡并导致代码崩溃或更糟。该规则的含义是您需要弹出相同大小(以字节为单位)的值。

所以在这种情况下,你在调用malloc:

之前将8个字节压入堆栈
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