平时在编写STM32的时候调用了一下函数用了多少栈空间呢?
先说答案:并非一个固定值。
在 Keil 编译器环境中,函数调用时的入栈空间(Stack Usage)并非一个固定值,而是取决于目标架构(如 ARM Cortex-M)、编译器、优化等级、函数参数数量与类型、局部变量大小以及是否发生中断嵌套(注:本文暂不讨论中断嵌套)。
在基于ARM Cortex-M内核的项目中(使用 ARM Compiler 5/6 或 GCC),栈空间主要用于保存上下文信息、函数参数、局部变量和返回地址。
单次函数调用的基本开销
当一个函数被调用时,入栈操作通常包含以下部分:
返回地址 (LR):4 字节。硬件自动将链接寄存器 (LR) 压入栈中,以便函数执行完后知道返回哪里。
寄存器保护 (Context Saving):如果被调用的函数使用了 R4-R11 等 callee-saved 寄存器,编译器会生成代码将这些寄存器压栈。每个寄存器 4 字节。如果函数内部调用了其他函数,可能还需要保存更多上下文。
参数传递: ARM 调用约定规定,前 4 个参数通过寄存器 (R0-R3) 传递,不占用栈空间。第 5 个及以后的参数会通过栈传递,每个参数占用 4 字节(即使 char 或 short 也会对齐到 4 字节)。重点:前4个参数不占用栈空间。
局部变量:函数内部定义的局部变量(非 static)全部存储在栈帧中。注意:大型数组或结构体会显著增加栈消耗。例如 uint8_t buffer[100] 会消耗 100 字节栈空间。
对齐填充 (Alignment):ARM 架构要求栈指针 (SP) 始终保持 8 字节对齐。因此,编译器可能会插入额外的填充字节以满足对齐要求。
中断服务程序 (ISR) 的特殊开销
当发生中断时,硬件会自动将部分寄存器压入栈(称为“自动压栈”或“Hardware Stacking”):
自动压栈内容:xPSR, PC, LR, R12, R3, R2, R1, R0。
空间占用:共 8 个寄存器 × 4 字节 = 32 字节
额外开销:如果在中断服务函数中调用了其他 C 函数,或者中断嵌套发生,还需要考虑软件压栈(保存 R4-R11 等)和新的栈帧开销。
理论上我们说完了,在实践过程中,我们又要如何直观的查看呢?Keil已经为我们准备好了静态分析与查看工具:
编译完成后,打开工程目录下的 .map 文件,搜索 Stack Usage 或 Call Graph 章节。Keil 会静态分析出每个函数的最大栈帧大小(Frame Size)和整个程序的最大栈需求(Maximum Stack Usage)。
文章写到这里,有人会问了:您这个都是在Bare-Metal模式下的理论,如果换成RTOS呢?这些理论知识还成立吗?那我们下回再讲。
欢迎大家关注我,我是你们的老朋友jobs。
我要赚赏金
