如何测量 C 中的函数堆栈使用情况?
Posted
技术标签:
【中文标题】如何测量 C 中的函数堆栈使用情况?【英文标题】:How to measure a functions stack usage in C? 【发布时间】:2015-04-13 20:13:53 【问题描述】:有没有一种方法可以测量一个函数使用了多少堆栈内存?
这个问题不是针对递归函数的;但是我很想知道递归调用的函数会占用多少堆栈内存。
我有兴趣优化堆栈内存使用的函数;但是,在不知道编译器已经进行了哪些优化的情况下,这只是猜测是否会进行真正的改进。
需要明确的是,这不是关于如何优化以更好地使用堆栈的问题
那么有没有一些可靠的方法可以找出函数在 C 中使用了多少堆栈内存?
注意:假设它没有使用alloca
或可变长度数组,
应该可以在编译时找到它。
【问题讨论】:
你可以。您需要找到描述您正在使用的平台的 ABI 以及给定语言类型的语言映射的文档。之后,您需要深入研究编译器的文档并找到有关组织堆栈帧和优化自动变量的实现细节。在阅读完所有这些内容后,您将简单地生成汇编输出并查看堆栈指针的实际使用情况,否则它会变得乏味且不准确...... 我还没有尝试过,但是如果你想动态地发现这一点,比如递归调用层次结构,我想到的一个想法是在你感兴趣的函数之前调用一个函数,它分配一个非常大的堆栈缓冲区并将其初始化为已知模式,例如 [0,1,2,3,4,5,6...,0,1,2,3,4,5...] 和然后调用一个伴随函数,它检查有多少已知模式仍然完好无损。当然,这不会精确到字节,但可以大致了解堆栈的使用情况。 “只生成汇编输出,看看栈指针是如何实际使用的” 如果你用gcc编译,你可以使用-S
选项生成一个汇编文件来自您的 .c 文件,您可以使用任何文本编辑器对其进行检查。另一种选择是使用显示汇编代码的调试器。这样,您可以单步执行代码并查看堆栈指针和基指针是如何使用的。
为什么要优化堆栈使用?这很奇怪,因为没有必要根据 c 标准进行堆栈实现。即使有,堆栈的使用方式完全取决于编译器和操作系统,
@ideasman42 您可以查看编译器手册以了解编译器特定的功能。例如如果您使用 gcc,您可以让它使用 -fstack-usage
标志告诉您每个函数的堆栈使用情况 - 您必须自己计算调用图的使用情况(例如,如果函数是递归的,乘以递归次数。)
【参考方案1】:
您可以通过以下方式很容易地找出调用只有一个局部变量单词的函数占用了多少堆栈空间:
static byte* p1;
static byte* p2;
void f1()
byte b;
p1 = &b;
f2();
void f2()
byte b;
p2 = &b;
void calculate()
f1();
int stack_space_used = (int)(p2 - p1);
(注意:该函数声明了一个只有一个字节的局部变量,但编译器通常会在堆栈上为其分配一个完整的机器字。)
因此,这将告诉您函数调用占用了多少堆栈空间。添加到函数中的局部变量越多,占用的堆栈空间就越多。在函数内不同范围内定义的变量通常不会使事情复杂化,因为编译器通常会在堆栈上为每个局部变量分配一个不同的区域,而不会基于其中一些变量可能永远不会共存的事实进行任何优化。
【讨论】:
我正在考虑做这样的事情,但你的例子有点简单。因为该函数可能有循环,在不同分支中定义的多个变量,调用内联函数......它并不总是像在块末尾添加单个变量并获取其地址那么简单,此外,编译器可能会重新订单变量 - ***.com/questions/238441/… 不,我再说一遍,大多数编译器并不关心你是在一个块中定义它们,还是在自己的块中定义它们。试试看。 @ddriver 分支完全不相关。大多数编译器都会为局部变量分配堆栈空间,就好像它们都在函数的根范围内声明一样。不相信我?试试吧。我发布了代码。就是这么简单。试试看。 @MikeNakis 并非所有编译器都同等优化。 @JimFell“不要这样做,因为编译器可能有错误”不是一个有效的论点。【参考方案2】:要计算当前函数的堆栈使用情况,您可以执行以下操作:
void MyFunc( void );
void *pFnBottom = (void *)MyFunc;
void *pFnTop;
unsigned int uiStackUsage;
void MyFunc( void )
__asm__ ( mov pFnTop, esp );
uiStackUsage = (unsigned int)(pFnTop - pFnBottom);
【讨论】:
你还能定义pFnBottom
和pFnTop
inside myFunc
吗?
@étale-cohomology 可能,但这可能会影响您的函数的堆栈使用。即使使用register
关键字也不能保证您的变量将存储在寄存器中。最可靠的方法是使用带有全局变量的实现。您可以将它们声明为静态以限制它们的范围。
谢谢!明白了。【参考方案3】:
使用警告
这是 GCC 特定的(使用 gcc 4.9 测试):
在函数上方添加:
#pragma GCC diagnostic error "-Wframe-larger-than="
报告错误,例如:
error: the frame size of 272 bytes is larger than 1 bytes [-Werror=frame-larger-than=]
虽然方法有点奇怪,但您至少可以在编辑文件时快速做到这一点。
使用 CFLAGS
您可以将-fstack-usage
添加到您的CFLAGS,然后将文本文件与目标文件一起写出。
见:https://gcc.gnu.org/onlinedocs/gnat_ugn/Static-Stack-Usage-Analysis.html
虽然这很好用,但根据您的构建系统/配置,它可能有点不方便——用不同的 CFLAG 构建单个文件,尽管这当然可以自动化。
–(感谢@nos 的评论)
注意,
似乎大多数/所有编译器的自然方法都依赖于猜测——优化后不能 100% 保证保持准确,因此这至少使用免费编译器给出了明确的答案。
【讨论】:
我尝试使用 -fstack-usage 标志,但出现编译器错误。你能提供一个如何使用这个标志的例子吗? @Karan2020 请发布参考链接 @vlad_tepesch 参考链接gcc.gnu.org/onlinedocs/gnat_ugn/… 已发布在答案中。我已将选项传递给 GCC 编译器。例如:gcc -c file_name.c -fstack-usage .以上是关于如何测量 C 中的函数堆栈使用情况?的主要内容,如果未能解决你的问题,请参考以下文章