STM32 程序内存占用:Flash 大小计算与 RAM 划分
目录
引言
在 Keil 开发 STM32 时,编译完成后会生成一个 .map 文件,文件末尾列出了 Total RO Size、Total RW Size、Total ROM Size 这几行数据。
这篇文章以 STM32F103C8T6 为例,把自己梳理的结果记录下来,主要搞清楚以下几个问题:
1.怎么看 .map 文件末尾的 Flash 和 RAM 占用?
2.这些数字分别代表什么?
3.RAM 里的 .data、.bss、堆、栈是怎么划分的?
第一部分:Flash 和 RAM 的分工
| 存储介质 | 存放内容 | 是否掉电保存 |
| Flash | 程序代码(.text)、常量(.rodata)、已初始化变量(.data 的初值) | 是 |
| RAM | 全局变量(.data / .bss)、堆、栈 | 否 |
第二部分:如何计算程序占用的 Flash 和 RAM
以 STM32F103C8T6(20KB RAM,64KB Flash)为例,计算程序占用的 Flash 和 RAM。程序编译完成后,查看工程中 Listings 文件夹内的 .map 文件,滚动到末尾即可看到汇总数据,例如:
Total RO Size (Code + RO Data) 7572 ( 7.39kB)
Total RW Size (RW Data + ZI Data) 4664 ( 4.55kB)
Total ROM Size (Code + RO Data + RW Data) 7612 ( 7.43kB)
| 项目 | 公式 | 说明 |
| RAM 占用 | RW Data + ZI Data | 程序运行时占用 RAM 的总量 |
| Flash 占用 | Code + RO Data + RW Data | 烧录时占用 Flash 的总量(包括代码和初始值的备份) |
上面的数据中涉及几个缩写,具体含义如下:
1.Code(代码段):程序编译后生成的指令码,也就是 CPU 要执行的机器指令。包括写的 C 代码、调用的库函数等。存在 Flash 中,只读。
2.RO Data(只读数据段):程序中使用到的常量数据,例如字符串常量、const 修饰的变量、中断向量表等。存在 Flash 中,只读。
3.RW Data(读写数据段):已初始化为非零值的全局变量和静态变量(如 int a = 5;)。这部分数据比较特殊:初始值备份存在 Flash 中,变量本身在程序启动时被拷贝到 RAM 中运行。所以在 Flash 和 RAM 的统计中,它都会出现。
4.ZI Data(零初始化数据段):未初始化或初始化为零的全局变量和静态变量。例如 int b;、int c = 0;、结构体 {0} 等。它只存在于 RAM 中,程序启动时由启动代码清零,不需要在 Flash 中备份初值。
为什么 RW Data 在 Flash 占用和 RAM 占用中都会出现?
以 int a = 5; 为例:
这个 5 在烧录程序时,作为初始值备份存放在 Flash 里。所以计算 Flash 占用时,必须加上这部分空间。
程序启动时,启动代码会把 Flash 里备份的 5 拷贝到 RAM 中 a 所在的地址。所以计算 RAM 占用时,也包含 a 这个变量本身的大小。
第三部分:RAM 内部的四大区域
RAM 内部被划分成四个逻辑区域,每个区域存放不同类型的数据。
| 区域 | 存放内容 | 生命周期 | 举例 |
| .data 段 | 已初始化为非零值的全局/静态变量 | 整个程序运行期间 | int a = 5; |
| .bss 段 | 未初始化或初始化为零的全局/静态变量 | 整个程序运行期间 | int b; 或 int c = 0; |
| 堆(Heap) | 动态分配的内存(malloc) | 手动 free 之前 | malloc(100) |
| 栈(Stack) | 局部变量、函数返回地址、中断现场 | 函数调用期间 | uint8_t buf[100]; |
注意:= {0} 会被编译器归入 .bss 段,因为它不需要在 Flash 中备份初值。
第四部分:栈和堆的大小如何调整?
栈大小 决定一次函数调用最多能容纳多少局部变量。如果局部变量过大或函数嵌套过深,栈空间不足会导致栈溢出,程序跳转到 HardFault。
堆大小 决定 malloc 最多能分配多少内存。如果堆空间不足,malloc 返回 NULL,程序访问空指针会死机。栈和堆的大小由启动文件(startup_stm32f10x_md.s)中的宏定义控制:
Stack_Size EQU 0x00000400 ; 1KB
Heap_Size EQU 0x00000200 ; 512B
实际场景举例: 当程序中使用了 cJSON 库解析 JSON 数据时,cJSON 内部会通过 malloc 动态分配内存来构建解析树。如果接收到的 JSON 数据较长,解析树占用的堆空间就会相应增大。此时如果 Heap_Size 仍然保持默认值(如 512B),堆空间可能被耗尽,导致 malloc 返回 NULL,程序访问空指针从而触发 HardFault 死机。因此,在使用 cJSON 等动态内存分配较多的库时,需要根据实际数据量适当增大 Heap_Size。
第五部分:基于.map画出 RAM 分布图
FSC8T6 的 RAM 大小是 20KB,地址范围为 0x20000000 ~ 0x20004FFF。 基于 .map 文件的实际占用数据,可以画出精确的分布图:

RAM 低地址 (0x20000000)
↓
┌─────────────────────────────────────────────────────────────────────┐
│ **.data 段** 0x20000000 ~ 0x20000027 (40 字节) │
│ ├─ stm32f10x_rcc.o 0x20000000 ~ 0x20000013 (20 字节) │
│ │ └─ APBAHBPrescTable (16B), ADCPrescTable (4B) │
│ ├─ fs800.o 0x20000014 ~ 0x20000018 (5 字节) │
│ │ └─ time_count (2B), timer_running_flag (2B), current_state (1B)│
│ ├─ PAD 0x20000019 ~ 0x2000001B (3 字节) │
│ ├─ stdout.o 0x2000001C ~ 0x2000001F (4 字节) │
│ │ └─ __stdout │
│ ├─ mvars.o 0x20000020 ~ 0x20000023 (4 字节) │
│ │ └─ __microlib_freelist │
│ └─ mvars.o 0x20000024 ~ 0x20000027 (4 字节) │
│ └─ __microlib_freelist_initialised │
├─────────────────────────────────────────────────────────────────────┤
│ **.bss 段** 0x20000028 ~ 0x20000C33 (3084 字节) │
│ ├─ serial1_rx 0x20000028 ~ 0x2000042D (1030 字节) │
│ ├─ serial2_rx 0x2000042E ~ 0x20000833 (1030 字节) │
│ └─ String 0x20000834 ~ 0x20000C33 (1024 字节) │
├─────────────────────────────────────────────────────────────────────┤
│ **PAD** 0x20000C34 ~ 0x20000C37 (4 字节) │ ← 地址对齐填充
├─────────────────────────────────────────────────────────────────────┤
│ **HEAP (堆)** 0x20000C38 ~ 0x20000E37 (512 字节) │
│ 起始: 0x20000C38 结束: 0x20000E37 │
│ __heap_base = 0x20000C38 │
│ __heap_limit = 0x20000E38(边界值,指向堆结束后的第一个字节) │
├─────────────────────────────────────────────────────────────────────┤ ← 堆结束 / 栈起始(紧挨着)
│ **STACK (栈)** 0x20000E38 ~ 0x20001237 (1024 字节) │
│ 起始: 0x20000E38 结束: 0x20001237 │
│ __initial_sp = 0x20001238(栈顶边界值) │
│ 向下增长 │
├─────────────────────────────────────────────────────────────────────┤
│ **空闲 RAM** 0x20001238 ~ 0x20004FFF (~15.8 KB) │
└─────────────────────────────────────────────────────────────────────┘
RAM 高地址 (0x20004FFF)
第六部分:变量存储规则速查表
| 代码写法 | 存储段 | 是否占 Flash 备份空间 |
| int a = 5; | .data | 是(初始值需要备份) |
| int b; | .bss | 否(只占 RAM) |
| int c = 0; | .bss | 否(全零初始化,不需要备份) |
| static char buf[100]; | .bss | 否(只占 RAM) |
| 局部变量 uint8_t arr[100]; | 栈 | 否(函数返回后自动释放) |
第七部分:总结
1. 两个核心公式
Flash 占用大小 = Code + RO Data + RW Data(烧录时占用的总空间)
RAM 占用大小 = RW Data + ZI Data(运行时占用的总空间)
2. 三种数据段的划分
.data:非零初始化的全局/静态变量(Flash 和 RAM 各占一份)
.bss(即 ZI Data):未初始化 / 零初始化的全局/静态变量(只在 RAM 中)
堆 + 栈:运行时动态使用的内存(由启动文件中的宏定义控制大小)
3. 内存管理思路
Flash 只要总量不超就行:具体内部怎么排布由编译器处理。
RAM 需要重点规划:.data 和 .bss 是静态占用的,堆和栈是动态使用的。如果堆或栈不够,编译不报错,但运行时会死机。
4. 关于 .map 文件
需要查看更详细的内存分布(如具体变量的地址、各段在 Flash 中的位置),可以直接打开 .map 文件,搜索 Memory Map of the image 部分。里面的 Execution Region ER_IROM1 对应 Flash 分布,Execution Region RW_IRAM1 对应 RAM 分布。
更多推荐


所有评论(0)