STM32:keil如何查看编译后代码及堆栈大小,堆栈空间溢出触发HardFault_Handler解决及调试办法
一、前言
最近在研究面向对象编程,在多个结构体内定义了数组,最终在main函数一起定义结构体对象的时候程序崩溃,直接触发了HardFault_Handler错误。
二、如何定位问题
打开如下调试窗口,发现程序一运行就卡死在HardFault_Handler函数,这时我们需要查看以下几个寄存器内容
- SP(R13):栈指针寄存器,存储当前栈顶地址,栈向下生长时,局部变量越大,SP 值越小
- LR(R14):链接寄存器,存储函数返回地址,函数执行完后,CPU 通过 LR 的值跳回上一级函数,栈溢出时 LR 值会异常。(图来源于M3手册)

查看发现SP和MSP寄存器都为0x1FFFFE80,这时打开Memory搜索0x1FFFFE80,发现弹出来的都是问号(证明访问地址非法),因此判断可能是堆栈溢出导致。

这时打开我们的启动文件查看堆栈大小,发现默认堆栈大小只有1K,而当前堆栈指针已经到了0x1FFFFE80,很明显的堆栈溢出导致的,因此只需要查看函数内部是否定义了大数组,如果定义了把它移出函数内部,定义为全局变量就好了。
三、补充内容1:栈、堆、全局区的区别
1.栈到底是什么以及存放哪些内容
栈是 MCU 里一块自动管理、向下生长的内存,由 CPU 硬件控制,专门存:
-
函数内部的局部变量(所有函数)
-
函数返回地址(所有函数)
-
寄存器现场
-
中断现场
默认大小:STM32F103 默认只有 1KB,所有函数、中断共用这 1KB!
2.与其它区的区别
|
区域 |
大小 |
特点 |
适合放什么 |
|
栈 Stack |
1KB |
自动管理、向下生长、空间小 |
小局部变量(int、指针) |
|
全局区 RAM |
20KB |
永久存在、不占栈、空间大 |
大数组、缓存、结构体 |
|
堆 Heap |
剩余 RAM |
手动 malloc/free |
动态数据,裸机基本不用 |
|
Flash |
64KB |
只读、掉电不丢 |
代码、常量、字库、图片 |
一句话总结:程序的全局变量属于RAM(可读可写)区,常量属于FLASH(只读不可写)区(const修饰的),程序代码属于FLASH区,函数内部的局部变量、返回地址、寄存器现场以及中断现场都属于栈。
四、补充内容2:如何验证栈的生长方向
很简单,在main内部前后定义两个不同大小的数组,观察前对堆栈指针(指向当前栈顶)的指向地址就好了,下面直接上图,后面做解释


实测 SP 指针:
-
buf[10] → SP = 0x200002D8
-
buf[20] → SP = 0x200002C8
差值为0x2D8-0x2C8=16字节,且数组越大,SP 越小,因此栈是向下生长的!(由0x400往下递减)。
看到这里可能大家有个问题,数组大小明明差了20个字节,为什么最后差值却是16字节?
原因:ARM Cortex-M 硬件强制:栈必须按 8 字节对齐,编译器自动补齐空间。
当定义buf[10]数组实际占用20字节,但因20不是8的倍数,因此编译器会给数组分配24字节的空间,而buf[20]数组实际占用40字节,刚好是8的倍数,因此实际差值为40-24=16字节。
五、补充内容3:HardFault_Handler常见错误原因
STM32出现HardFault_Handler硬件错误的原因主要有两个方面:
1、内存溢出或者访问越界。(包括使用野指针)
2、堆栈溢出。(如本篇博客)
主要看LR寄存器内容

详细解决办法可以看下面两篇文章:
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)