2016-07-07 61 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增加了延迟。 –

相关问题