在x86程序集中,是否可以从堆栈中删除值而不存储它? add esp,4
的某些内容?我显然可以使用{{1}},但也许有一个很好的,干净的cisc助记符,我错过了?
答案 0 :(得分:8)
add esp,4
/ add rsp,8
是正常/惯用/清洁方式。不需要特殊的方式,因为堆栈不是神奇的或特殊的(至少在这方面不是这样);它只是一个寄存器中的指针,其中一些指令隐含地使用它。 (对于内核堆栈,中断使用它是异步的,因此软件即使想要实现内核红区也不会......)
除此之外,在函数末尾清理整个堆栈帧的神奇CISC方法是leave
= mov esp, ebx
/ pop ebp
(或16位或64位)当量)。与enter
不同,它在现代CPU上足够快,可以在实践中使用,但仍然可以在Intel CPU上使用3 uop指令。 (http://agner.org/optimize/)。但是leave
只在第一时间起作用,如果你花费额外的指令制作一个首先使用ebp
/ rbp
的堆栈框架。 (通常你不会这样做,除非你需要保留一定数量的堆栈空间,例如在循环中使用push
来制作数组,或者相当于C99 VLA或{{1}或者,对于初学者代码,可以更容易地访问本地,或者在16位模式下alloca
不能用于寻址模式。)
清理stack-args的神奇CISC方法是让被调用者使用ret imm16
(花费1个额外的uop)来弹出args,创建一个调用约定,其中被调用者清理堆栈。在调用者 - 弹出式调用约定中,没有办法使用这种形式的SP
,但是您可以简单地保留堆栈偏移并使用ret
来存储下一个函数调用的args mov
的函数(如果函数需要任何stack-args; register-arg调用约定通常更有效。)
因此,神奇的CISC方式在现代CPU上没有性能优势,只有较小的代码大小。
您可以使用push
代替pop reg
有两个原因:
add esp,4
是一个单字节指令,pop r32/r64
为3个字节,add esp,4
为4个字节。性能:在堆栈指令(push / pop / call / ret)后显式使用add rsp,8
/ esp
时,Intel的堆栈引擎必须插入额外的堆栈同步uops )。因此,在rsp
(使用call
返回)之后,它会保存一个uop,以便在ret
之前使用pop
代替add esp,4
功能
ret
加载/存储,需要单独的uop来进行堆栈指针修改。并在堆栈指针上创建数据依赖。 有关效果的详情,请参阅Why does this function push RAX to the stack as the first operation?。
如果您正在寻找美学,那么您可以很好地缩进,格式化和评论您的代码,但除了,如果美学超过优化,当您选择x86 asm时,您选择了错误的语言。
当然,如果您需要将堆栈调整超过1个寄存器宽度,如果您不需要mov
将加载的数据,请务必使用add
。或者,如果您需要将其调整为+128字节,请使用pop
,因为sub esp, -128
可编码为符号扩展的imm8,但+128不是
或者也许使用-128
,就像gcc与lea esp, [esp+4]
一样。 (对于有序原子,而不是silvermont)。就像我说的,如果你想干净,你就不应该选择x86 asm。
您几乎总能找到-mtune=atom
进入的无效注册。如果您需要在弹出一些实际想要弹出的寄存器之前将E / RSP调整一个堆栈插槽,您可以随时弹出相同的寄存器两次。
在非常罕见的情况下,7(x86-32)或15(x86-64)非堆栈寄存器都不可用作pop
目的地,此优化不可用,您应该只需使用传统的pop
。不值得花费额外的指示来add
;这将超过使用pop
的微小好处。
请注意pop
(段寄存器)仍然使用常规"堆栈宽度" (32或64位,取决于模式),而不是16位寄存器只有16位。 But only pop ds/es/ss
are single-byte. pop fs/gs
are 2 bytes each。因此,如果您针对代码大小进行优化,pop Sreg
比pop gs
小1个字节,但速度要慢得多。 (或者比add esp,4
小2个字节。)