首先,我注意到这里很大程度上依赖于 C++ 最常见的实现方式。我将要描述的内容几乎总是适用于 Intel x86 或任何远程类似设备上的任何实际实现——但它可能会在 IBM 大型机或早期 Cray(两者都不真正支持堆栈在硬件中)。就此而言,它并不能真正保证在任何东西上工作 - 但它会在大多数当前硬件上按预期工作(尤其是:几乎任何带有 Intel x86 处理器的东西)。
这很大程度上取决于我们是尝试从内部程序还是从外部(即,从另一个程序)执行此操作。
程序内部
如果我们是从程序内部进行的,我们可以从 main 开始,如下所示:
char *stack_bottom;
int main() {
int a;
stack_bottom = &a;
这为我们提供了一个位于堆栈底部的地址(嗯,从技术上讲,并不完全是底部,但无论如何都非常接近它)。
然后在我们想知道当前堆栈深度的任何给定点,我们可以调用一个类似这样的函数:
ptrdiff_t stack_size() {
char a;
return abs(stack_bottom - &a);
}
这会在堆栈顶部创建一个变量,然后将两者相减以获得大小。堆栈通常在内存中向下增长,因此堆栈的“顶部”通常位于比顶部低的地址。由于我们只关心差异的大小,因此我们采用差异的绝对值(尽管如果我们知道堆栈增长的方向,我们可以避免这种情况)。
Windows 特定
由于这似乎是针对 Windows,我们可以使用 VirtualQuery 来获取有关堆栈内存的类似信息。
计划之外
从程序的“外部”来看,基本的想法几乎相同:找到栈底,找到栈顶,减去找到大小。但是,很有可能我们必须找到不同的顶部和底部。
不过,这往往会进入更多特定于操作系统的领域。例如,在 Windows 上,您可能会使用 StackWalk64 遍历堆栈并告诉您顶部和底部的地址。在 Linux 上,您通常会改用 backtrace。在其他一些操作系统上,您很有可能会使用具有其他名称的函数,以其他格式生成数据。但大多数都会按照相同的一般顺序提供一些东西。
一旦我们得到了栈顶和栈底的地址,我们又回到了几乎相同的情况:将栈顶和栈底的地址相减得到大小。
使用调试器
另一种可能性是在调试器下运行程序。虽然我们通常希望在进行任何调试之前运行启动代码,但在这种情况下,我们可以只加载程序,并查看 ESP 寄存器以查看堆栈从哪里开始。然后当我们想找到我们打破的堆栈大小时,获取 ESP 中的值并(当然)减去以获得大小。
警告
如果您想了解技术,我在第一个中展示的内容不会正好是堆栈的整个大小。通常有一些启动例程执行诸如检索环境变量和命令行、按预期格式化它们、调用全局对象的构造函数等,它们在main 之前运行。所以有一些空间不会被捕获。但是它缺少的是一个很小的(通常是固定的)数量,所以通常我们根本不会关心它。但是如果我们真的需要知道确切的数量,我们可以(例如)查看启动的源代码来弄清楚。