12

在 x86 汇编中,是否可以从堆栈中删除一个值而不存储它?类似的东西pop word null?我显然可以使用add esp,4,但也许我缺少一个漂亮而干净的 cisc 助记符?

4

1 回答 1

17

add esp,4/add rsp,8 正常的/惯用的/干净的方式。不需要特殊的方法,因为堆栈并不神奇或特殊(至少在这方面不是);它只是寄存器中的一个指针,其中包含一些隐式使用它的指令。(对于内核堆栈,中断异步使用它,因此即使软件想要实现内核红区也无法实现......)

除此之外,在函数末尾清理整个堆栈帧的神奇 CISC 方法是leave= mov esp, ebp/ pop ebp(或 16 位或 64 位等效项)。与 不同enter的是,它在现代 CPU 上足够快,可以在实践中使用,但在 Intel CPU 上仍然是 3 uop 指令。(http://agner.org/optimize/)。但leave只有在您首先花费额外的指令使用ebp/制作堆栈帧时才有效rbp。(通常你不会这样做,除非你需要保留可变数量的堆栈空间,例如push在循环中创建一个数组,或者相当于 C99 VLA 或alloca. 或者对于初学者代码,以使访问本地人更容易,或者在SP不能用于寻址模式的 16 位模式下。)

清理堆栈参数的神奇 CISC 方法是让被调用者使用ret imm16(花费 1 个额外的 uop)来弹出参数,创建一个调用约定,被调用者清理堆栈。在 caller-pops 调用约定中,没有办法使用这种形式的ret,但您可以简单地保留堆栈偏移量并用于mov存储 args 以供下一个函数调用,而不是push(如果函数需要任何堆栈参数;注册-arg 调用约定通常更有效。)

所以神奇的 CISC 方式在现代 CPU 上没有性能优势,只有较小的代码大小。


您可以使用 2 个原因pop reg来代替add esp,4

  • 代码大小:pop r32/r64是一个单字节指令,而 . 3 字节add esp,4或 4 字节add rsp,8.
  • 性能:当您在堆栈指令(push/pop/call/ret)之后显式使用esp/时,英特尔的堆栈引擎必须插入额外的堆栈同步微指令。rsp因此,在 a call(返回 a )之后,它会在函数末尾ret保存一个 uop 以供使用pop,而不是add esp,4在你之前使用。ret

    AMD 的堆栈引擎不需要额外的堆栈同步微指令,但仍会生成推送/弹出单微指令。与旧的 Intel/AMD CPU 不同,push/pop 的成本高于普通mov的加载/存储,需要单独的 uop 来修改堆栈指针。并创建对堆栈指针的数据依赖。

请参阅为什么此函数将 RAX 作为第一个操作推送到堆栈?有关性能的更多详细信息。

如果您正在寻找美学,那么您可以很好地缩进、格式化和注释您的代码,但是如果美学胜过优化,那么当您选择 x86 asm 时,您选择了错误的语言


当然,如果您需要将堆栈调整超过 1 个寄存器宽度,add如果您不需要pop要加载的数据,请务必使用。或者,如果您需要将其调整 +128 字节,请使用sub esp, -128, 因为-128可编码为符号扩展的 imm8,但 +128 不是。

或者也许使用lea esp, [esp+4],就像 gcc 对-mtune=atom. (对于有序原子,而不是silvermont)。就像我说的,如果你想要干净,你不应该选择 x86 asm。


您几乎总能找到一个死寄存器pop进入. 如果您需要在弹出一些您实际想要弹出的寄存器之前将 E/RSP 调整一个堆栈槽,您总是可以弹出同一个寄存器两次。

在极少数情况下,7 (x86-32) 或 15 (x86-64) 非堆栈寄存器都不能用作pop目标,这种优化不可用,您应该简单地使用传统的add. 不值得花费额外的说明来使其成为可能pop;这将超过使用pop.

请注意,pop Sreg(段寄存器)仍然消耗常规的“堆栈宽度”(32 位或 64 位,具体取决于模式),而不是 16 位寄存器仅消耗 16 位。 但只有pop ds/es/ss单字节。 pop fs/gs每个 2 个字节。因此,如果您正在优化代码大小,pop gs则 1 字节比 小add esp,4,但要慢得多。(或比 小 2 个字节add rsp,8)。

于 2018-02-09T12:25:14.310 回答