为什么我们需要堆栈清理代码,它是如何工作的?(汇编语言)

Why do we need stack cleanup code and how does it work?(In assembly language)

栈是一种遵循后进先出法则的数据结构。在汇编语言中,当我们调用一个函数时,我们需要使用 "push" 指令将参数压入堆栈。但是为什么我们需要堆栈清理代码来删除参数呢?大多数堆栈清理代码看起来像

add esp N

如何从堆栈中删除参数?

The stack pointer register (ESP) points to the top of the stack. Since the stack grows down, increasing the value of ESP removes items from the stack. Changing the value of ESP is the only way to remove items from the stack, whether explicitly with an ADD or LEA instruction or implicitly with POP or RET

- Ross Ridge

对于向下增长的堆栈,这意味着它从更高的地址开始,当您执行入栈指令时,它会从堆栈指针中减去一些值。该函数需要 return 它找到的堆栈(指针)。所以你可以做一堆弹出指令,在这种情况下添加到堆栈指针地址,或者你可以简单地将推送的数量添加到堆栈指针。或者另一种解决方案是您可以以不干扰调用者的方式保存堆栈指针并简单地恢复该值。一堆 pop 指令通常会浪费指令 space 和执行时间,尤其是当一条 add 指令可以有效地做同样的事情时。

以任何方式清理堆栈并不意味着实际恢复您在堆栈上弄乱的 ram,根据定义,堆栈指针下方的 ram 是公平的游戏,它只是意味着 return 将堆栈指针指向它被称为状态。

我先回答OP问题的第一部分:为什么?因为对于大多数 CPU 而言,堆栈既包含当前函数的本地存储(局部变量和参数),也包含控制信息(return 地址、指向前一个堆栈帧的指针等)

当你调用一个不清除自身堆栈的函数时(例如,遵守 cdecl 调用约定的函数),调用者负责在被调用者 returns 之后离开堆栈,在与调用前相同的状态。这意味着如果调用者将 N 个字节压入堆栈,它必须从堆栈中删除 N 个字节,或者通过弹出并丢弃它们,或者更快地通过直接修改堆栈指针的值跳过这些字节(即, ADD SP,N 指令)。否则,堆栈会随着每个未清理的调用函数而增长,最终会发生堆栈溢出。