【方辉专栏】ARM嵌入式编译器(七) C/C++的堆栈使用
发布时间:2022-08-22

摘要: 本文主要对C/C++的堆栈使用进行介绍。

关键字:堆栈、堆栈的预估


1. C/C++中的堆栈

在C和C++都会使用到堆栈。

例如:

函数的返回地址。

Arm 架构的过程调用标准(AAPCS) 或Arm 64 位架构的过程调用标准(AAPCS64) 必要的寄存器。例如,当进入子程序时寄存器内容需要被保存。

局部变量,包括局部数组、结构体和联合体。

C++ 中的类。

一些堆栈的使用并不明显,例如:

如果局部整数或浮点变量被溢出(即未分配给寄存器),则为它们分配堆栈内存。

结构体通常分配给堆栈。在堆栈上保留相当于填充多个字节的空间,其中用于AArch64 状态或AArch32 状态。但是,编译器可能会尝试将结构体分配给寄存器。 sizeof(struct)nn168

如果在编译时知道数组的大小,则编译器会在堆栈上分配内存。同样,在堆栈上保留了相当于填充 {n} 个字节的倍数的空间,其中用于AArch64 状态或AArch32 状态。 sizeof(array)n168


2. 估计堆栈的使用

堆栈的使用量很难估计,因为它取决于代码的编写,并且在运行时可能会有所不同,具体取决于程序执行时所采用的代码路径。但是,可以使用以下方法手动估计堆栈利用率:

编译-g并链接--callgraph以生成静态调用图。此调用图显示所有函数的信息,包括堆栈使用情况。

链接或列出所有全局符号的堆栈使用情况。--info=stack--info=summarystack

使用调试器在堆栈中的最后一个可用位置设置观察点,并查看观察点是否被命中。使用选项编译-g以生成必要的 DWARF 信息。

注: Debugging With Attributed Record Formats(DWARF)使用带属性的记录格式进行调试。


利用调试器:

1)为比您预期需要的大得多的堆栈分配内存空间。

2)用已知值的副本填充堆栈空间,例如0xDEADDEAD.

3)运行您的应用程序,并在测试中使用尽可能多的堆栈空间。例如,尝试执行最深嵌套的函数调用和静态分析发现的最坏情况路径。尝试在适当的地方生成中断,以便将它们包含在堆栈跟踪中。

4)应用程序完成执行后,检查内存的堆栈空间以查看有多少已知值已被覆盖。该空间在已使用的部分中有数据,在剩余部分中是已知值。

5)计算有数据值的数量(以字节为单位)。sizeof(value)


使用与目标处理器或架构相对应的固定虚拟平台 (FVP)。使用映射文件,在堆栈正下方定义一个禁止访问的内存区域。如果堆栈溢出到禁止区域,则会发生数据中止,调试器可以捕获该异常。


3. 检查堆栈的使用

检查程序中函数使用堆栈的大小是一个良好的编程习惯。这样可以写出使用较小堆栈的代码。

要检查程序中的堆栈使用情况,需要使用--info=stack这个链接器选项。

__attribute__((noinline)) int fact(int n)

{

  int f = 1;

  while (n>0)

  {

    f *= n--;

  }

  return f;

}

int foo (int n)

{

  return fact(n);

}

int foo_mor (int a, int b, int c, int d)

{

 return fact(a);

}

int main (void)

{

  return foo(10) + foo_mor(10,11,12,13);

}

将代码示例复制到file.c并使用以下命令对其进行编译:

armclang --target=arm-arm-none-eabi -march=armv8-a -c -g file.c -o file.o

使用该选项进行编译会-g生成armlink估计堆栈使用所需的 DWARF 帧信息。使用以下命令在目标文件上运行armlink:--info=stack

armlink file.o --info=stack


对于示例代码,armlink显示了各种函数使用的堆栈数量。Function foo_mor比 function foo 有更多的参数,因此使用更多的堆栈。

Stack Usage for fact 0xc bytes.

Stack Usage for foo 0x8 bytes.

Stack Usage for foo_mor 0x10 bytes.

Stack Usage for main 0x8 bytes.


您还可以使用链接器选项检查堆栈使用情况--callgraph:

armlink file.o --callgraph -o FileImage.axf

这会输出一个名为的文件FileImage.htm,其中包含应用程序中各种函数的堆栈使用信息。

fact (ARM, 84 bytes, Stack size 12 bytes, file.o(.text))

[Stack]

Max Depth = 12

Call Chain = fact

[Called By]

>>   foo_mor

>>   foo

foo (ARM, 36 bytes, Stack size 8 bytes, file.o(.text))

[Stack]

Max Depth = 20

Call Chain = foo >> fact

[Calls]

>>   fact

[Called By]

>>   main

foo_mor (ARM, 76 bytes, Stack size 16 bytes, file.o(.text))

[Stack]

Max Depth = 28

Call Chain = foo_mor >> fact

[Calls]

>>   fact

[Called By]

>>   main

main (ARM, 76 bytes, Stack size 8 bytes, file.o(.text))

[Stack]

Max Depth = 36

Call Chain = main >> foo_mor >> fact

[Calls]

>>   foo_mor

>>   foo

[Called By]

>>   __rt_entry_main (via BLX)


4. 减少堆栈的使用的方法

减少堆栈使用量通常有以下几个方法:

1)编写只需要几个变量的小函数。

2)避免使用较大的局部结构体或数组

3)避免递归调用

4)函数在执行的任何特定时候都尽可能少的使用变量。

5)使用C块作用域语法并在需要的位置声明变量,这样可以在不同作用域使用相同内存。


来源:《Arm® Compiler for Embedded User Guide Version 6.18》

参考链接:DWARF 格式简介 https://gohalo.me/post/program-c-gdb-dwarf-format-introduce.html


+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


关于九游会J9电子

九游会J9电子技术有限公司(英文名称:Emdoor Electronics Technology Co.,Ltd)是国内资深的研发工具软件提供商,公司成立于 2002 年,面向中国广大的制造业客户提供研发、设计、管理过程中使用的各种软件开发工具,致力于帮助客户提高研发管理效率、缩短产品设计周期,提升产品可靠性。

20 年来,先后与 Altium、ARM、Ansys、QT、Adobe、Visu-IT、Minitab、Testplant、EPLAN、HighTec、GreenHills、PLS、Ashling、MSC Software 、Autodesk、Source Insight、TeamEDA、MicroFocus等多家全球知名公司建立战略合作伙伴关系,并作为他们在中国区的主要分销合作伙伴服务了数千家中国本土客户,为客户提供从芯片级开发工具、EDA 设计工具、软件编译以及测试工具、结构设计工具、仿真工具、电气设计工具、以及嵌入式 GUI 工具等等。九游会J9电子凭借多年的经验积累,真正的帮助客户实现了让研发更简单、更可靠、更高效的目标。


欢迎关注“九游会J9电子”公众号

了解更多研发工具软件知识