31

有没有办法在 C 编译时知道并输出函数所需的堆栈大小?这是我想知道的:

让我们采取一些功能:

void foo(int a) {
    char c[5];
    char * s;
    //do something
    return;
}

编译此函数时,我想知道调用它时会消耗多少堆栈空间。这对于检测隐藏大缓冲区的结构的堆栈声明可能很有用。

我正在寻找可以打印这样的东西:

文件 foo.c : 函数 foo 堆栈使用是n字节

有没有办法不查看生成的程序集来知道这一点?或者可以为编译器设置的限制?

更新:我不是试图避免给定进程的运行时堆栈溢出,我正在寻找一种在运行前查找的方法,如果编译器确定的函数堆栈使用情况可用作编译过程的输出。

让我们换一种说法:是否有可能知道函数本地所有对象的大小?我猜编译器优化不会是我的朋友,因为一些变量会消失,但一个更好的限制是好的。

4

7 回答 7

11

Linux 内核代码在 x86 上的 4K 堆栈上运行。因此他们关心。他们用来检查的是他们编写的 perl 脚本,您可以在最近的内核 tarball 中找到它作为 scripts/checkstack.pl(2.6.25 有它)。它在 objdump 的输出上运行,使用文档在初始注释中。

我想我很久以前就已经将它用于用户空间二进制文件了,如果你知道一点 perl 编程,如果它坏了,很容易修复它。

无论如何,它基本上所做的就是自动查看 GCC 的输出。内核黑客编写了这样一个工具的事实意味着没有静态的方式来使用 GCC(或者它可能是最近添加的,但我对此表示怀疑)。

顺便说一句,使用 mingw 项目中的 objdump 和 ActivePerl,或者使用 Cygwin,您应该能够在 Windows 以及使用其他编译器获得的二进制文件上执行此操作。

于 2009-01-12T08:59:27.627 回答
8

StackAnlyser 似乎检查了可执行代码本身以及一些调试信息。这个回复所描述的是我正在寻找的东西,堆栈分析器对我来说看起来有点矫枉过正。

类似于 ADA 存在的东西就可以了。从 gnat 手册中查看此手册页:

22.2 静态栈使用分析

使用 -fstack-usage 编译的单元将生成一个额外的文件,该文件指定每个函数使用的最大堆栈量。该文件与目标对象文件具有相同的基本名称,扩展名为 .su。该文件的每一行由三个字段组成:

* The name of the function.
* A number of bytes.
* One or more qualifiers: static, dynamic, bounded. 

第二个字段对应于函数帧的已知部分的大小。

限定符 static 意味着函数帧大小是纯静态的。这通常意味着所有局部变量都具有静态大小。在这种情况下,第二个字段是函数堆栈利用率的可靠度量。

限定符动态意味着函数帧大小不是静态的。它主要发生在某些局部变量具有动态大小时。当这个限定符单独出现时,第二个字段不是函数堆栈分析的可靠度量。当它有界时,意味着第二个字段是函数堆栈利用率的可靠最大值。

于 2008-09-24T10:58:06.273 回答
4

我不明白为什么静态代码分析不能为此提供足够好的数字。

在任何给定函数中查找所有局部变量是微不足道的,并且每个变量的大小可以通过 C 标准(对于内置类型)或通过计算它(对于结构和联合等复杂类型)来找到。

当然,不能保证答案是 100% 准确的,因为编译器可以进行各种优化,例如填充、将变量放入寄存器或完全删除不必要的变量。但它给出的任何答案至少应该是一个很好的估计。

我做了一个快速的谷歌搜索,发现了 StackAnalyzer,但我猜其他静态代码分析工具也有类似的功能。

如果您想要一个 100% 准确的数字,那么您必须查看编译器的输出或在运行时检查它(就像 Ralph 在他的回复中建议的那样)

于 2008-09-24T10:44:15.560 回答
1

只有编译器才会真正知道,因为它是把你所有的东西放在一起的人。您必须查看生成的程序集并查看序言中保留了多少空间,但这并不能真正说明alloca在运行时执行它们的操作。

于 2008-09-24T08:34:48.663 回答
1

假设您在嵌入式平台上,您可能会发现您的工具链在这方面有所作为。良好的商业嵌入式编译器(例如 Arm/Keil 编译器)通常会生成堆栈使用报告。

当然,中断和递归通常有点超出它们的范围,但是如果有人在堆栈上的某个地方使用数兆字节的缓冲区犯了一些可怕的错误,它会给你一个粗略的想法。

于 2008-09-24T11:03:48.647 回答
1

不完全是“编译时间”,但我会将此作为构建后步骤:

  • 让链接器为您创建一个映射文件
  • 对于map文件中的每个函数,读取可执行文件的对应部分,并分析函数序言。

这类似于 StackAnalyzer 所做的,但要简单得多。我认为分析可执行文件或反汇编是获得编译器输出的最简单方法。虽然编译器在内部知道这些东西,但恐怕您无法从中获得它(您可能会要求编译器供应商实现该功能,或者如果使用开源编译器,您可以自己做或让别人做为你)。

要实现这一点,您需要:

  • 能够解析地图文件
  • 理解可执行文件的格式
  • 知道函数序言的样子并能够“解码”它

这将是多么容易或困难取决于您的目标平台。(嵌入式?哪个 CPU 架构?什么编译器?)

所有这些都可以在 x86/Win32 中完成,但如果您从未做过类似的事情并且必须从头开始创建所有这些,则可能需要几天时间才能完成并让某些东西正常工作。

于 2008-09-24T11:39:20.637 回答
0

一般不会。理论计算机科学中的停止问题表明,您甚至无法预测通用程序是否会在给定输入上停止。计算用于程序运行的堆栈通常会更加复杂。所以不行。也许在特殊情况下。

假设您有一个递归函数,其递归级别取决于可以是任意长度的输入,而您已经不走运了。

于 2008-09-24T09:43:05.597 回答