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