栈本身没有已分配和未分配空间的概念,这完全取决于惯例。由于栈是您(当前例程)、操作系统和所有加载库之间共享的资源,因此必须有一些“社会”规则来避免事情失控。这些规则是:
- 不要谈论堆栈规则。
- 每个人都可以随意减少堆栈指针,无需询问任何人。
- 如果将堆栈指针减小X,则在结束时必须将其逐步增加X,且只能增加X。不能多,不能少。
所以如果这是我们的栈状态:
Stack decrements in this direction ==>
___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___
... |___|___|___|___|___|___|___|___|___|___|___| ...
^
|
Stack pointer ---+
根据第二条规则,我们知道堆栈指针右侧的所有内容都是不安全的。因为在操作系统中断或调用子程序时,这些内存位置将被覆盖。
我们可以说SP左侧的内容是已分配的,而右侧的内容是未分配的。
Stack decrements in this direction ==>
.
Allocated : Unallocated
:
:
___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___
... |___|___|___|___|___|___|___|___|___|___|___| ...
^
|
Stack pointer ---+
如果我们想要分配一些空间,我们可以再次使用第二条规则并仅减少堆栈指针。
如果我们恰好有要写入新分配空间的值,我们可以优化这个过程并使用
push
。
这就是您提供的代码正在发生的事情。
push ebp ;Saving ebp
mov ebp, esp ;Saving esp into ebp
sub esp, 4 ;Saving four bytes onto the stack
提供堆栈状态的函数
Stack decrements in this direction ==>
.
Allocated : Unallocated
:
4 bytes reserved --+ :
___ ___ ___ ___ _V_ ___ ___ ___ ___ ___ ___
... |___|___|___|EBP|___|___|___|___|___|___|___| ...
^ ^
Frame pointer -+ |
Stack pointer ---+
现在你肯定明白了:
我们通过移动栈指针在栈上分配/释放内存。
如何移动它并不重要:使用
push
、
sub
、
and
、
add
或
pop
都是操作栈指针以在栈上分配/释放内存的示例。
您可以自由选择其中任何一个,或者选择最适合您需要的,但是不能忘记已经通过第三条规则分配了什么。
这就是为什么最终会有未回收的内存。
mov esp, ebp ;Restoring the saved value of esp
pop ebp ;Restoring the value of ebp from the stack
这段代码的作用是将堆栈指针恢复到
EBP位置。这本可以使用
add esp, 04h
命令,但事情有时会变得复杂,最好使用第一种方法。
因为我们不再关心指针下面(右边)存储的值,所以我们可以使用
add
命令,而不是一系列
pop
命令将其弹出到未使用的寄存器中,只需将堆栈指针放回原位即可释放内存。如果要从堆栈中获取值,就必须使用
pop
命令,就像必须恢复
EBP寄存器的值一样。
理解函数前导和后继需要考虑的关键是,不是发生了什么,而是在一个调用内部另一个调用时会发生什么。如果你能从两者返回,那么你就完全掌握了它。
1随着操作系统拥有自己的私有特权堆栈,这不再是特权上下文切换的问题,但出于清晰起见,请与我保持一致。
call
和ret
指令)。 - stakx - no longer contributing