堆栈内存消耗如何计算?

Posted

技术标签:

【中文标题】堆栈内存消耗如何计算?【英文标题】:How is stack memory consumption calculated? 【发布时间】:2019-02-11 23:31:15 【问题描述】:

我需要计算我的程序的堆栈内存消耗。 gcc 的-fstack-usage 仅计算函数的堆栈使用情况,但据我了解,该函数中不包含额外的函数调用。

void test1()
    uint32_t stackmemory[100];
    function1();                    //needs aditional stack, say 200 Bytes
    uint32_t stackmemory2[100];


void test2()
    uint32_t stackmemory[100];
    uint32_t stackmemory2[100];
    function1();                   //needs additional stack, say 200 Bytes

哪个 test() 函数使用更少的堆栈?我会说 test1(),因为在 function1() 调用之后堆栈被释放。还是这取决于优化级别 -Os/-O2 ...?

编译器是否在 test1() 中为它的所有静态变量分配内存,一旦输入函数?还是到达该行时分配stackmemory2[100]?

【问题讨论】:

是什么阻止了您进行实验? @P__J__:实验结果并不是编译器规范的可靠指标;未来潜在的软件变更;或因命令行开关、源代码或其他情况的变化而导致的变化。 @EricPostpischil 我的 DV 线索正确吗? @EricPostpischil 如果你说的是唯一的答案,那么 OP 现在可能会放弃,不是吗?但是编译器基本上是确定性的,否则使用它们进行软件开发是不可能的。实验结果将是有用的信息,但必须在特定设置和环境(如您提到的那些)中进行测量和记录,而不是将其视为适用于所有可能编译的简单常量。 @barny 这显然是个挑剔的人。一般来说,C 不知道堆栈是什么。我期待更多类似的cmets。例如 70 年代早期苏联无堆栈计算机制造的堆栈:) 【参考方案1】:

通常,您需要将调用图信息与-fstack-usage 生成的.su 文件结合起来,以从特定函数开始找到最深的堆栈使用情况。从main() 或线程入口点开始,将为您提供该线程的最坏情况。

使用来自here 的 Perl 脚本,已按照 here 的讨论为您完成了创建此类工具的工作。

ARM 的 armcc 编译器(在 Keil ARM-MDK 中使用)具有此功能 built-in,并且可以在链接映射中包含详细的堆栈分析,包括最坏情况的调用路径和非确定性堆栈使用的警告(例如由于递归)。

根据我观察多个编译器行为的经验,通常为函数的生命周期定义堆栈帧,而不管声明的变量的生命周期和范围如何。所以这种情况下的两个版本不会有区别。如果不声明它们volatile,优化器可能会在任何情况下删除这两个数组。但是,您不应该依赖在这方面的任何观察结果是通用的 - 它是实现而不是语言定义。

【讨论】:

@jonnyx 当然欢迎你,但要明确的是,我只是用谷歌搜索了它——我从未使用过它——希望它对你有用。【参考方案2】:

哪个 test() 函数使用更少的堆栈?我会说 test1(),因为 function1() 调用后释放堆栈。还是这取决于 优化级别 -Os/-O2...?

它们在堆栈上分配完全相同的内存。并且恰好在同一时刻:)

test1:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 800 <- on stack allocation
        mov     eax, 0
        call    function1
        nop
        leave
        ret
test2:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 800 <- on stack allocation
        mov     eax, 0
        call    function1
        nop
        leave
        ret

唯一的区别是优化开启时未使用的变量将被完全删除。

编译器是否在 test1() 中为其所有静态分配内存 变量

您的示例中没有任何静态变量,只有自动变量

静态变量不会在堆栈上分配,因为它们在函数调用之间保持其值。

godbolt.org/z/j9-VQq

【讨论】:

此汇编代码不能是问题中显示的代码编译的结果,因为问题中显示的代码由于多种原因无法编译。通过一些简单的添加,我能够使用 Apple 的 LLVM 产生类似的结果——但只是没有优化。通过优化,堆栈使用量从大约 800 个字节下降到大约 16 个字节。所以这个答案给出了一个不正确的堆栈使用图。 谢谢,你没有忘记函数 1 的额外 200 字节吗?这 800 字节来自两个 stackmemory[100] 变量的分配,正如 -fstack-usage 的输出所告诉我的那样。所以这个函数的总堆栈消耗是 1000 对吗?在这两种情况下,在函数调用之后是否再次释放消耗的内存是否无关紧要? 毫无疑问必须包含某些内容。这不会改变您提供的程序集不是使用问题中显示的代码生成并且没有说明差异的事实。您在答案中留下了信息。此外,我并没有说该遗漏使答案无效(由于其他原因,答案无用),而只是说清楚,要正确讨论代码,必须提供完整的信息 . 对答案的主要抱怨不是它缺少重现它声称的结果的信息,而是它呈现的结果在实际软件中无用 - 未优化代码的结果不会在生产中使用.它省略了关于优化如何以各种方式影响堆栈使用的讨论。例如,一个函数同时包含int x[300];int y[300];,但使用它们的生命周期完全不相交,编译器应优化为两个数组仅使用 300 个int P__J__:为了支持您的回答,请参阅标准的第 6.2.4/6 条,该条适用,因为数组不是可变长度的。这实质上需要在块的开头为两个数组保留空间。当然,有人可能会争辩说,as-if 规则可以允许该要求无效。我想不出一种在分配之前观察分配的方法,它不涉及 UB,但证明没有这样的机制让我觉得很困难。 (当然,我们知道优化编译器可能会完全消除未使用的变量。)

以上是关于堆栈内存消耗如何计算?的主要内容,如果未能解决你的问题,请参考以下文章

内存的堆栈分析

java 堆栈内存分析详解

堆栈与ESP(栈指针寄存器)

c++堆栈的各自大小,堆和栈的各自定义

内存里的堆和栈只读区静态全局区

WebAssembly:复制栈顶