如何知道堆栈函数消耗了多少

How to know how much stack function is consuming?

本文关键字:多少 函数 何知道 堆栈      更新时间:2023-10-16

>最近,我在一次采访中遇到了这个问题:
我们如何确定特定函数在堆栈上消耗了多少存储空间?

"堆栈"是平台的著名实现细节,无法从语言本身内部进行检查或以任何方式查询。基本上不可能在 C 或 C++ 程序的任何部分保证是否可以进行另一个函数调用。"堆栈大小",或者更好地称为"函数调用和局部变量存储深度",是语言标准承认其存在的实现限制之一,但被认为超出了范围。(例如,C++见[限制],附件B。

个别平台可能会提供 API 来允许程序内省平台限制,但 C 和 C++ 都没有具体说明这一点或应该如何做到这一点。

超出

实现定义的资源限制会导致未定义的行为,并且您无法知道是否会超出限制。

它是完全实现定义的 - 该标准不会以任何方式对程序使用的可能底层机制施加要求。

在 x86 机器上,一个堆栈帧由返回地址(4/8 字节(、参数和局部变量组成。

参数,例如标量,可以通过寄存器传递,因此我们不能确定它们是否有助于占用的存储空间。当地人可能会被填充(而且通常是(;我们只能推断出这些的最小存储量。

确定的唯一方法是实际分析编译器生成的汇编代码,或者在运行时查看堆栈指针值的绝对差异 - 在调用特定函数之前和之后。

例如

#include <iostream>
void f()
{
    register void* foo asm ("esp");
    std::cout << foo << 'n';
}
int main()
{
    register void* foo asm ("esp");
    std::cout << foo << 'n';
    f();
}

现在比较输出。海湾合作委员会关于科里鲁给予

0x7fffbcefb410
0x7fffbcefb400

相差 16 个字节。(堆栈在 x86 上向下增长。

正如其他答案所述,程序栈是一个没有在语言本身中指定的概念。但是,了解了典型实现的工作原理后,您可以假设函数的第一个参数的地址是其堆栈帧的开头。下一个被调用函数的第一个参数的地址是下一个堆栈帧的开头。因此,他们可能希望看到这样的代码:

void bar(void *b) {
   printf("Foo stack frame is around %lld bytesn", llabs((long long)b - (long long)&b));
}
void foo(int x) {
  bar(&x);
}

对于那些使用堆栈的实现,堆栈的大小增加为:

  • 不适合可用寄存器的变量的大小
  • 在预先声明的函数
  • 中声明的变量的大小,这些变量在函数的生命周期内有效
  • 沿途或在语句块中声明的其他局部变量的大小
  • 此函数调用的函数使用的最大堆栈大小
  • 以上所有内容 * 递归调用的数量
  • 退货地址的大小

退货地址

大多数实现在任何其他数据之前推送堆栈上的返回地址。 所以这个地址占用空间。

可用寄存器

某些处理器具有许多寄存器;但是,只有少数寄存器可用于传递变量。 例如,如果约定允许 2 个变量,但有 5 个参数,则将在堆栈上放置 3 个参数。

当大型对象按值传递时,它们将占用堆栈上的空间。

函数局部变量

这很难计算,因为变量可能会被推到堆栈上,然后在不使用时弹出。

某些变量在声明之前可能不会被推送到堆栈上。 因此,如果函数中途返回,它可能不会使用剩余的变量,因此这些变量的堆栈大小不会增加。

编译器可以选择使用寄存器来保存值或将常量直接放入可执行代码中。 在这种情况下,它们不会向堆栈添加任何长度。

调用其他函数

该函数可以调用其他函数。 每个调用的函数都可能增加堆栈上的数据量。 那些被调用的函数可以调用其他函数,依此类推。

这同样取决于执行时间的快照。 但是,可以通过其他称为函数的堆栈产生近似的最大增加。

递归

与调用其他函数一样,递归调用可能会增加堆栈的大小。 函数末尾的递归调用可能比开头附近的递归调用增加堆栈更多。

寄存器保值

有时,编译器可能需要比分配的寄存器允许的更多的数据空间。 因此,编译器可以在堆栈上推送变量。

为方便起见,编译器可能会在堆栈上推送寄存器,例如交换寄存器或更改值的顺序。

总结

函数所需的堆栈空间的确切大小很难计算,并且可能取决于执行的位置。 在堆栈大小计算中需要考虑许多项目,例如参数数量和大小以及调用的任何其他函数。 由于可变性,大多数堆栈大小测量基于最大大小或最坏情况大小。 堆栈分配通常基于最坏情况。

对于面试问题,

我会提到以上所有内容,这通常会让面试官想要快速进入下一个问题。