【STM32】Keil中的Code,RO,RW,ZI以及堆栈

本文改自?链接: Keil中的Code,RO,RW,ZI分别表示什么
为方便了解,增加了一些内容。

keil在编译完成之后会出现这么一段文字。
在这里插入图片描述
这些参数的单位是Byte
Code:代码的大小
RO:常量所占空间,存储const常量和指令
RW:程序中已经初始化的变量所占空间,存储初始化值不为0的全局变量。
ZI:未初始化的static和全局变量以及堆栈所占的空间,存储未初始化的全局变量或初始化值为0的全局变量

上述参数和芯片Flash以及SRAM的对应关系是
Flash占用大小=Code+RO+RW
SRAM占用大小=RW+ZI

由于SRAM在掉电的时候数据会丢失,所以在主flash启动模式,上电后上电后首先从0x00000000(映射到0x08000000,这里只考虑从内部Flash启动)处获得中断向量表然后在运行用户代码之前会在标号2处有一段引导代码bootloder负责把存在Flash中的初始化变量的值Copy到SRAM中对应的变量位置(标号3),之后把ZI区域全部清零(标号4),之后才正式开始运行用户代码(标号5)。

所以在计算时RW要使用两次。
在这里插入图片描述
平常所说的堆栈指的是栈Stack,在单片机的启动文件中可以看到,单片机上电的时候会在RAM中分配一段(一般是高地址)空间,这就是栈。

栈主要用于存放局部变量,函数形参等,属于编译器自动分配和释放的内存,栈的大小不能超过内部 SRAM 的大小。如果工程的程序量比较大,定义的局部变量比较多,那么就需要在启动代码中修改栈的大小,即修改 Stack_Size 的值。如果程序出现了莫名其妙的错误,并进入了 HardFault 的时候,你就要考虑下是不是栈空间不够大,溢出了的问题。

栈是从高往低生长,所以结束地址就是栈顶地址。

<think>我们正在讨论STM32内存映射中不同数据段的分配(CodeROData、RWData、ZIData)以及如何调试它们。这些段对应于编译后程序的不同内存区域,对于理解程序如何使用内存和优化内存占用至关重要。首先,我们回顾一下这些段的具体含义:-**Code(代码段)**:存储程序的可执行代码(机器指令),位于FLASH存储器中。-**ROData(只读数据段)**:存储常量数据(如const修饰的全局变量、字符串常量等),位于FLASH中。-**RWData(读写数据段)**:存储已初始化的全局变量和静态变量,这些变量在程序启动时被初始化。注意:RWData在FLASH中存储初始值,但运行时位于RAM中。-**ZIData(零初始化数据段)**:存储未初始化或初始化为0的全局变量和静态变量,位于RAM中,程序启动时由启动代码初始化为0。整个程序的内存占用计算如下:-FLASH占用=Code+ROData+RWData(因为RWData的初始值需要存储在FLASH中)-RAM占用=RWData+ZIData(因为RWData和ZIData都在RAM中)在调试过程中,我们可能需要查看这些段的大小,以确保它们没有超出芯片的可用内存,特别是对于RAM占用可能导致的堆栈溢出问题。##1.获取各段的大小信息通常在构建项目后,编译器会生成一个映射文件(.map文件),其中详细列出了各个段的大小和位置。###在KeilMDK中:-编译链接后,在构建输出窗口中会显示类似如下的信息:```ProgramSize:Code=xxxxRO-data=xxxxRW-data=xxxxZI-data=xxxx```-更详细的信息可以查看生成的.map文件,在项目中打开.map文件,通常位于输出文件夹(如Objects文件夹),其中会列出每个模块的代码和数据的大小,以及全局符号的地址。###在STM32CubeIDE(基于GCC)中:-编译后,在控制台输出中可以看到类似:```MemoryregionUsedSizeRegionSize%ageUsedFLASH:xxxxByyyyKBzz%RAM:aaaaBbbbbKBww%```-但这只是总的使用情况,要获取更详细的分段信息,我们可以使用`arm-none-eabi-size`工具。在CubeIDE中,通常已经集成了该工具,可以在项目的Debug或Release文件夹下找到生成的.elf文件,然后在终端中运行:```basharm-none-eabi-size-A-xyour_project.elf```-输出将包含各个段(.text,.data,.bss等)的详细信息,其中:-.text:代码段(Code)-.rodata:只读数据段(ROData)-.data:RWData(已初始化的全局/静态变量)-.bss:ZIData(未初始化的全局/静态变量)注意:这里的.data对应RWData,但需要注意的是,RWData在FLASH中有备份(初始值),所以占用FLASH和RAM,而.bss只占用RAM(启动时初始化为0)。##2.调试方法###调试内存溢出如果怀疑内存溢出(特别是堆或栈溢出),我们可以:-**查看.map文件中的内存布局**:确定堆栈区域的位置和大小,以及全局变量的位置。注意堆栈通常位于RAM的末尾(从高地址向低地址增长),而堆在ZI段之后(从低地址向高地址增长)。-**使用调试器监视堆栈指针**:在运行过程中,如果堆栈指针(SP)超出了为堆栈分配的区域,通常会导致内存访问错误(硬错误),此时进入HardFault中断。通过调试器(如ST-Link)可以在发生异常时暂停程序,查看SP的值以及当前函数调用栈,从而定位溢出位置。-**使用堆栈保护机制**:一些RTOS提供任务栈溢出检测功能,比如在栈顶放置特殊标记值(如0xDEADBEEF),定期检查该标记是否被覆盖。如果没有RTOS,也可以手动在栈底附近放置标记值,然后定期检查。-**使用MPU(内存保护单元)**:一些STM32微控制器具有MPU,可以设置内存区域为只读或禁止访问,当堆栈溢出并访问到该区域时触发异常。###查看变量位置在调试器中,可以通过查看变量的地址来判断它所属的数据段:-全局变量(已初始化)地址在RAM区域(RWData),其初始值保存在FLASH中。-全局变量(未初始化)地址在ZI段(即.bss段)。-常量数据(如字符串)地址在FLASH中(ROData)。##3.优化各段内存占用-**Code段优化**:-编译器优化选项(如-Os优化尺寸,-O3优化速度但可能增加代码尺寸)。-去掉不必要的函数或模块。-使用函数指针表或查表代替大switch语句。-**ROData优化**:-减少字符串常量的使用,或合并相同的字符串(编译器通常自动合并)。-使用更紧凑的数据表示方法(如用枚举代替字符串)。-**RWData优化**:-减少全局变量,使用局部变量(栈空间)代替。-将只读的变量用const修饰,使其移到ROData段(减少RAM占用,但增加FLASH占用)。-**ZIData优化**:-初始化全局变量,以避免它们在.bss中?实际上,初始化不会减少ZIData,只是将变量从ZI段移到RW段(但会占用额外的FLASH空间存储初始值)。因此,仅当变量确实需要非零初始值时才初始化。-减少大型全局数组(特别是缓存)的大小,或使用动态内存分配(但需注意堆碎片问题)。##总结理解STM32的内存分段及其占用情况对于调试和优化程序至关重要。我们应当:1.定期查看编译输出和.map文件。2.在开发阶段进行内存溢出测试(如堆栈使用测试)。3.合理优化程序结构以减少内存占用。§§相关问题§§1.如何计算STM32程序的FLASH和RAM占用?2.STM32启动过程中如何初始化RWData和ZIData?启动代码的流程是怎样的?3.在STM32中如何使用MPU来检测堆栈溢出?4.如何优化STM32FLASH占用(特别是CodeROData)?</think> 以下详细解释STM32中的内存数据段(CodeRO Data、RW Data、ZI Data)分配原理和调试方法,结合编译原理和STM32内存管理机制进行说明: ### 1. 数据段分配原理(基于ARM编译模型) | **数据段** | **存储内容** | **物理位置** | **初始化方式** | |------------|--------------|--------------|----------------| | **Code** | 可执行代码(机器指令) | FLASH | 上电直接执行 | | **RO Data**| 常量(const变量/字符串) | FLASH | 上电只读访问 | | **RW Data**| 已初始化全局变量 | RAM(运行时) + FLASH(初始值) | 启动文件`__main`复制初始值 | | **ZI Data**| 未初始化全局变量/初始化为0变量 | RAM(运行时) | 启动文件`__main`清零 | ``` 内存分布示意图: ┌─────────────┐ ┌─────────────┐ │ FLASH │ │ RAM │ ├─────────────┤ ├─────────────┤ │ Code │ │ RW Data │ │ │ │ (运行时值) │ ├─────────────┤ ├─────────────┤ │ RO Data │ │ ZI Data │ │ (常量) │ │ (初始为0) │ ├─────────────┤ └─────────────┘ │ RW Data │ │ (初始值) │ └─────────────┘ ``` - **总占用量计算** FLASH占用 = Code + RO Data + RW Data (初始值) RAM占用 = RW Data (运行时) + ZI Data 例如Keil编译输出: ``` Program Size: Code=24560 bytes, // FLASH RO-data=4240 bytes, // FLASH RW-data=320 bytes, // RAM(运行时) + FLASH(初始值) ZI-data=2800 bytes // RAM Total FLASH = 24560 + 4240 + 320 = 29120 bytes Total RAM = 320 + 2800 = 3120 bytes ``` ### 2. 调试方法与实践 #### (1) 查看段信息 - **Keil MDK** 编译后输出窗口显示段大小 打开`.map`文件搜索`Memory Map of the image`获取详细地址分布 - **STM32CubeIDE(GCC)** 使用`arm-none-eabi-size`工具: ```bash arm-none-eabi-size -A build/project.elf ``` 输出示例: ``` section size addr .text 0x4000 0x08000000 // Code .rodata 0x800 0x08004000 // RO Data .data 0x200 0x20000000 // RW Data .bss 0x1000 0x20000200 // ZI Data ``` #### (2) 运行时监测 - **地址检查** 在调试器中查询变量地址判断所属段: ```c const int ro_var = 10; // RO Data (0x0800xxxx) int rw_var = 20; // RW Data (0x2000xxxx) int zi_var; // ZI Data (0x2000xxxx) ``` - **边界监测** 在RW Data后添加哨兵值,运行时检查是否被覆盖: ```c volatile uint32_t rw_end_marker = 0xDEADBEEF; ``` #### (3) 链接脚本分析(.ld) 以STM32F4为例的部分关键配置: ```ld MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K } SECTIONS { .text : { *(.text*) } > FLASH /* Code段 */ .rodata : { *(.rodata*) } > FLASH /* RO Data段 */ .data : { _sdata = .; /* RW Data起始地址 */ *(.data*) _edata = .; /* RW Data结束地址 */ } > RAM AT > FLASH /* 运行时在RAM,初始值在FLASH */ .bss : { _sbss = .; /* ZI Data起始地址 */ *(.bss*) _ebss = .; /* ZI Data结束地址 */ } > RAM } ``` ### 3. 优化策略 - **减少RO Data** 避免大量字符串常量,用`printf("Hello")`替代`const char str[] = "Hello"` - **压缩RW Data** 初始化大型数组时优先用常量初始化(移到RO Data) - **优化ZI Data** 减少大数组定义,使用动态内存分配(需平衡堆空间) > 案例:某传感器项目通过将历史数据缓冲区从RW Data(直接定义数组)改为堆分配(ZI Data+Heap),节省16KB FLASH空间[^2] ### 4. 关键调试工具 1. **STM32CubeMonitor** 实时监控RAM使用率,检测溢出 2. **SEGGER Ozone** 可视化内存分布图(需J-Link调试器) 3. **OpenOCD+pyOCD** 脚本化检查内存状态: ```python target.read_memory(0x20000000, 100) # 读取RW Data区域 ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值