1. MCU 内存概述
1.1 MCU 内存计算
以 STM32F103CBT6
为例,在 Keil 中可以看见,该款型号 MCU 的 Flash, RAM 大小为:
可以看出 Flash 的地址范围为 0x08000000 ~ 0x08010000,大小为 0x10000(164 byte = 216 byte,所以为 26 KB = 64 KB),同理 RAM 的地址范围为 0x20000000 ~ 0x20005000,大小为 0x5000(5 * 163 byte = 5 * 212 byte,所以为 5 * 22 KB = 20 KB)
1.2 内存分布结构
- 64KB Flash(用于存放
.text
和.rodata
) - 20KB RAM(用于
.data
、.bss
、栈和堆)
程序的典型内存分布:
stm32 由于是 32 位地址总线,所以地址位用八位十六进制数表示(168 = 232)。
pgsql复制编辑+------------------+ 0x08010000 (Flash 结束)
| .rodata | 只读数据(Flash)
+------------------+
| .text | 代码段(Flash)
+------------------+ 0x08000000 (Flash 起始)
+------------------+ 0x20005000 (RAM 结束)
| Heap | 动态分配(malloc)
+------------------+
| Stack | 栈(局部变量、返回地址)
+------------------+
| .bss | 未初始化数据(RAM)
+------------------+
| .data | 已初始化全局变量(RAM)
+------------------+ 0x20000000 (RAM 起始)
注意:
.text
和.rodata
只能存放在 Flash(代码和只读常量)。.data
段在 Flash 里存有初值,但运行时会被复制到 RAM。.bss
段不会占用 Flash,但会在 RAM 里初始化为 0。- 栈和堆共享 RAM,栈 Stack 从 高地址 0x20005000 向低地址 方向增长,堆 Heap 从 低地址 0x20000000 向高地址 方向增长。
- 当 Stack 和 Heap 彼此增长到一个临界点(即二者相遇),就会导致 Stack Overflow(栈溢出)。
2.ARM 编译器中的存储字段
在 Keil 或 ARM GCC 编译器的 Program Size 统计信息中,列出了四个关键字段:
- Code(.text) → 程序代码
- RO-data(.rodata) → 只读数据
- RW-data(.data) → 已初始化的全局/静态变量
- ZI-data(.bss) → 未初始化的全局/静态变量
示例:Keil 编译器的存储统计
上图显示了,该程序的内存统计信息如下:
- Code=13996 byte
- RO-data=312 byte
- RW-data=156 byte
- ZI-data=12156 byte
3. 存储区域的详细解析
MCU 代码和变量在存储时会被划分到不同的区域,各个区域用途如下:
存储区域 | 用途 | 存储位置 |
---|---|---|
.text(Code) | 存放编译后的程序指令(代码) | Flash(只读) |
.rodata(RO-data) | 存放只读常量(const 变量等) | Flash(只读) |
.data(RW-data) | 已初始化的全局/静态变量 | Flash + RAM |
.bss(ZI-data) | 未初始化的全局/静态变量,启动时清零 | RAM(自动清零) |
Heap(堆) | malloc() 动态分配的内存 | RAM(向上增长) |
Stack(栈) | 局部变量、函数调用返回地址 | RAM(向下增长) |
3.1 .text段(存储 code)
- 存储内容:存放程序指令,即 CPU 执行的代码。
- 存储位置:通常存放在 Flash(ROM),因为代码是固定的,不需要在运行时修改。
- 特点:
- MCU 复位后,程序从 Flash 读取指令并执行。
- 代码段通常是只读的,防止意外修改。
- C 语言的函数和中断服务函数都存放在这里。
- 示例:
void func(void) { // func 的指令代码存放在 .text 段
// do something
}
3.2 .rodata 段 (存储 RO-data,Read-Only Data)
- 存储内容:存放只读数据,如
const
修饰的全局变量、字符串常量等。 - 存储位置:通常存放在 Flash,因为这些数据不会被修改。
- 特点:
- 只能被读取,不能修改。
- 对于资源有限的 MCU,尽量让数据存放在 Flash,而不是占用 RAM(不需要改变的常量可以用 const 修饰)。
- 示例
const char message[] = "Hello, World!";
3.3 .data 段(存储 RW-data,Read-Write Data )
存储内容:存放已初始化的全局变量/静态变量。
存储位置:
- 编译时:数据存放在 Flash。
- 程序运行时:MCU 启动代码(Startup 文件)会在程序启动时,将
.data
段的数据从 Flash 复制到 RAM,确保变量可读写。
所以已初始化的全局变量会同时占用 Flash 和 RAM 的内存空间
-
特点:
- 变量必须初始化,否则存放在
.bss
段。 - 既占用 Flash 又占用 RAM(启动时复制)。
- 变量必须初始化,否则存放在
-
示例:
int a = 10; // 存放在 .data 段(Flash + RAM)
static int b = 20; // 也存放在 .data 段(Flash + RAM)
3.4 .bss 段(存储 ZI-data,Zero Initialized Data ,)
- 存储内容:存放未初始化的全局变量和静态变量。
- 存储位置:直接存放在 RAM。
- 特点:
- 上电后,系统会自动清零(由 Startup 代码完成)。
- 变量未初始化时占用 RAM 但不会占用 Flash,因为 Flash 里没有初始值。
- 示例
int global_var; // 存放在 .bss 段,自动初始化为 0
static int static_var; // 也是 .bss 段,自动初始化为 0
3.4 堆(Heap)和栈(Stack)
除了上述四个标准段,MCU 运行时还包含 堆(Heap) 和 栈(Stack),它们都位于 RAM,但用法不同。
存储区域 | 作用 | 增长方向 |
---|---|---|
堆(Heap) | 存放 malloc() 动态分配的数据 | 向上增长 |
栈(Stack) | 存放局部变量、函数调用返回地址 | 向下增长 |
示例:
void foo(void) {
int local_var = 10; // local_var 在栈(stack)
int *ptr = malloc(10); // malloc 分配的内存在堆(heap)
}
4. 代码优化建议
4.1 尽可能使用 const
修饰全局变量:
- 让编译器将变量放入 Flash(节省 RAM)。
const int lookup_table[] = {1, 2, 3, 4}; // 存在 Flash,而不是 RAM
4.2 避免全局变量初始化为 0
- 让变量默认存放到
.bss
,减少 Flash 和 RAM 的同时占用。
int a = 0; // 额外占用 Flash
int b; // 更节省空间,存放在 .bss,自动初始化为 0
4.3 避免 malloc(),使用静态分配
- 不要频繁使用
malloc()
,嵌入式系统中 RAM 资源有限,推荐使用静态数组。
// 避免动态分配,改为静态数组
int buffer[256];
5. 总结
Code(.text):存放程序代码(存储在 Flash)。
RO-data(.rodata):存放只读数据(存储在 Flash)。
RW-data(.data):存放已初始化的全局变量(Flash 复制到 RAM)。
ZI-data(.bss):存放未初始化的全局变量(RAM,上电后自动清零)。
Heap(堆):malloc()
动态分配内存(RAM)。
Stack(栈):存放局部变量、函数调用(RAM)。
ROM (Flash) size = Code + Ro-data + rw-data;
RAM size = Rw-data + zi-data