如何确定最大堆栈使用量?

Posted

技术标签:

【中文标题】如何确定最大堆栈使用量?【英文标题】:How to determine maximum stack usage? 【发布时间】:2010-09-28 04:50:25 【问题描述】:

有哪些方法可用于确定嵌入式/内存受限系统的最佳堆栈大小?如果它太大,则浪费了可以在其他地方使用的内存。但是,如果它太小,那么我们得到这个网站的同名......

尝试快速开始:Jack Ganssle 在 The Art of Designing Embedded Systems 中指出,“通过经验,人们可以学习标准、科学的方法来计算堆栈的适当大小:Pick一个随机的尺寸和希望。” 谁能做得比这更好?

要求提供更具体的示例。那么,如何使用IAR Embedded Workbench 工具链在没有操作系统的情况下针对具有 2 kB RAM 的MSP430 MCU 的 C 程序呢?此 IDE 可以在使用 JTAG 调试器时显示堆栈内容和使用情况。

【问题讨论】:

取决于您使用的芯片组/操作系统/编程语言。 很高兴看到这个问题有一些答案,不像***.com/questions/177516/… 我在提问时看到了这个问题,但认为嵌入式倾斜将它们分开...... Jack Ganssle 不仅如此。我认为那只是他的介绍单。摘自他的书,第 2 版,p。 250:“由于很少有程序员有合理的方法来确定最大堆栈要求,所以总是假设你的估计是不正确的。对于系统中的每个堆栈,确保初始化代码用值 0x55 填充分配给堆栈的全部内存量. 以后调试的时候可以查看堆栈,通过在那个区域看不到0x55的块来检测堆栈溢出..." 我知道他说的还不止这些,但我试着用他做的同样的方式来使用:激发对这个话题的兴趣。 ;) 【参考方案1】:

您使用静态分析标记了您的问题,但这是一个很难通过静态分析解决的问题。堆栈使用取决于程序的运行时配置文件,尤其是在您使用递归或 alloca 时。鉴于这是一个嵌入式平台,我想运行 pstop 之类的东西并查看您的应用程序正在使用多少堆栈也很困难。

一种有趣的方法是使用当前堆栈帧的地址来确定使用了多少堆栈。您可以通过获取函数参数或局部变量的地址来做到这一点。对 main 函数和您认为使用最多堆栈的函数执行此操作。差异将告诉您应用程序所需的堆栈数量。这是一个示例(假设习惯上从高到低的堆栈增长)。

char *stack_top, stack_bottom;

int
main(int argc, char *argv[])

    stack_top = (char *)&argc;
    // ...
    printf("Stack usage: %d\n", stack_top - stack_bottom);


void
deeply_nested_function(void)

    int a;
    stack_bottom = (char *)&a;
    // ...

如果您的编译器允许您指定自定义函数序言(许多这样做是为了允许基于图形的程序分析),您甚至可以安排所有函数调用此类测量代码。然后你的测量功能类似于

void
stack_measurement_function(void)

    int a;
    stack_bottom = min(stack_bottom, (char *)&a);
    // ...

我使用类似于我所描述的方法来生成these charts。

【讨论】:

那么,运行时测试就是答案吗?选择一大堆,确定上限后再减少? 既然你在回答中说“这是一个很难通过静态分析解决的问题”,我指的是absint.com/stackanalyzer【参考方案2】:

确定最深堆栈使用的最常见方法是使用一些已知但不寻常的值初始化堆栈内存,然后定期(或在大型测试运行结束时)查看该模式停止的位置。

这正是 IAR IDE 确定使用的堆栈数量的方式。

【讨论】:

没错,它用 0xCD 填充堆栈。 我以前也使用过这种技术。诀窍是在 pre-main 中(或在 main 中从当前堆栈帧之后的某个点)绘制堆栈到您的限制,然后从堆栈限制向后计数到绘制内存被破坏的位置(不是 0xCD) 你实际上是怎么做到的呢?我在***.com/questions/1740888/…问了一个关于普通PC的等效问题。 @JXG - 对于 Windows 程序,这不太可行;堆栈内存由操作系统管理,因此您实际上没有很好的机会来绘制堆栈。该解决方案更适合嵌入式系统,其中任务的堆栈通常由应用程序管理。我必须考虑如何在 Windows 上安全地执行此操作。 @Michael - IAR IDE 和嵌入式系统中用于确定堆栈数量的有趣方法。很好,您指出在 Windows 中堆栈内存由操作系统管理,因此没有很好的机会来绘制/着色堆栈! +1。【参考方案3】:

使用好的源代码静态分析工具,您可以为您的应用程序生成调用图。鉴于此,以及编译器生成的本地/临时数量的估计,您可以直接计算堆栈需求的保守估计。

我所说的“好”分析工具是一种可以读取所有涉及的编译单元,可以确定直接函数调用,可以确定间接指针,在编译单元中,可以计算保守点分析的工具系统,可以构建一个考虑到点分析的调用图。这消除了许多工具,这就是为什么人们会看到诸如“在运行时填充堆栈并查看会发生什么”之类的临时方法。您还需要估计编译器在堆栈上放置的堆栈需求;您可以通过简单地了解所有类型的存储需求有多大来估算其中的大部分内容,这对于嵌入式系统 C 程序通常相当容易确定。最后,您需要相信您的应用程序没有递归调用,或者该工具具有最深递归的想法(可能通过您告诉它)。

DMS 软件再造工具包满足 C 程序的所有这些要求。 见http://www.semanticdesigns.com/Products/DMS/DMSToolkit.html 您仍然必须对其进行配置,以通过爬取调用图并使用各种大小估计来计算堆栈需求。

如果您想快速获得答案,请使用堆栈填充技巧。如果您想要一个可以在每次源代码更改后重新计算的答案,您将需要静态分析方法。

【讨论】:

【参考方案4】: 不要使用递归或递归算法永远。 (注意正则表达式库) 不要使用数组,始终使用 malloc()。 不要使用 alloca(),有些编译器甚至在这个函数中存在错误。

然后手动检查这部分代码,寻找堆栈使用率可能最高的位置(记住我说没有数组)

检查代码本身怀疑 高点的堆栈使用情况,记录到调试器界面。 根据估计的堆栈使用情况设置一个上限,并就位。例如限制服务器连接。

【讨论】:

这个问题被标记为嵌入。因此,您关于永远不要“使用数组,始终使用 malloc()”的建议尤其不合适,但在任何硬件上,我认为这通常是不好的建议。为什么你总是只从堆中分配? 很难想象你为什么没有删除这个答案。 不要以零理由给出糟糕的规定性建议。【参考方案5】:

我现在正在解决这个问题 - 即堆栈大小的分析计算。 这显然是一段高度递归的代码,因为函数调用可以将索引数组作为其一个或多个参数,并且一个或多个数组索引可能涉及函数调用!!

但是,有几个实现可以让您从复杂性中解脱出来:

(1) 使用高级语言编译器时,每个语句/代码行执行结束时的堆栈指针应与开始时的位置相同。 (至少这是一个很好的规则,否则你会遇到问题!)

(2)每个函数或子程序调用返回后的堆栈指针应与调用前相同。因此,最大堆栈大小是程序中所有语句中每个语句达到的峰值堆栈大小的最大值。 (至少这是一个很好的规则,否则你会遇到问题!)

当然,一个语句可以包含我上面提到的递归问题,但至少找到整个程序的最大堆栈大小要求的问题归结为找到每个语句的最大堆栈大小要求,然后选择最大值那些。

在所有调用的函数也都被编译之前,这无法完成。所以我为每个编译的模块生成一个文件,记录每个语句的堆栈大小 (基本上是每个函数调用之前的峰值和每个函数调用之前的值(不包括由函数调用引起的堆栈大小的任何未知添加),以及所涉及的函数名称。然后我使用递归回溯处理这些文件例程,一旦所有函数都被编译,以确定峰值堆栈大小。

幸运的是,除了递归例程之外,最大可能的堆栈大小要求并不取决于程序流,尽管在典型的流(取决于数据)中,可能永远不会达到最大可能的堆栈大小。

示例:假设函数 1 调用函数 2,并且两者的程序流程都依赖于数据值 X。假设存在一个 X 范围,导致函数 1 执行其最差的语句,其中包括对函数 2 的调用,该语句不会对相同范围的 X 执行其最坏情况语句。由于我们同时使用函数 1 和函数 2 的最坏情况来计算最大可能的堆栈大小,因此我们可能高估了堆栈大小。至少我们在安全方面犯了错误。

如果需要,我喜欢在 OS 堆栈上为中断例程提供自己的堆栈空间,因此它们不会增加程序堆栈要求,除了从中断返回的地址

【讨论】:

以上是关于如何确定最大堆栈使用量?的主要内容,如果未能解决你的问题,请参考以下文章

堆排序和建立最大堆

最大堆中的第 K 个最大元素

如何堆最大堆?

浅谈堆栈队列

java - 如何使用PriorityQueue集合从Java中的给定数组在O(n)时间内构建最大堆?

基于最大堆实现最大优先队列代码