内存布局
建议在阅读本文章之前先阅读以下文章:
程序的编译、链接过程
前置知识
- Flash (ROM/Program Memory):存储程序代码和常量。
- RAM (Data Memory):存储运行时的变量(全局、静态、局部)、堆栈数据。
- 链接器脚本 (Linker Script, .ld 文件):
告诉链接器如何将汇编器生成的各个目标文件中的“段”组合起来,最终映射到物理的 Flash 和 RAM 地址空间。 - 启动代码 (Startup Code):在 main 函数执行之前运行的初始化代码,负责关键的硬件初始化和最关键的内存初始化(初始化 .data 段,清零 .bss 段,设置堆栈指针)。
主要的内存段

1. .text (代码段)
作用:存储程序的可执行代码(机器指令)
存储位置:Flash (ROM)。因为代码是只读的,并且需要掉电后保留。
实现:编译器将 C/C++/汇编代码编译成机器指令
- 汇编器/编译器将这些指令放入文件的 .text 段
- 链接器根据链接脚本将所有目标文件的 .text 段合并,并定位到 Flash 的起始地址(通常是 0x08000000 或类似地址)
- 上电复位后,CPU 从 Flash 的 .text 段中取指令执行
2. .data (已初始化数据段)
作用:存储已初始化的全局变量和静态变量(包括函数内部的静态变量)。这些变量在程序开始运行时就有一个明确的非零初始值。
存储位置:
- 初始值存储在 Flash (ROM):编译器在编译时知道这些变量的初始值,所以这些初始值被当作常量数据存储
- 运行时这些变量会被复制到 RAM 中(由启动代码在 main() 执行前完成)
实现 (关键点):
- 编译器为每个已初始化的全局/静态变量预留 RAM 空间
- 编译器将这些变量的初始值提取出来,放入一个特殊的 .data 段(这个 .data 段最终会放在 Flash 中)
- 链接脚本做两件重要的事情:
- 定义 .data 段在 RAM 中的运行时起始地址 (_sdata) 和结束地址 (_edata)
- 定义 .data 段的初始值在 Flash 中的存储位置 (_sidata)
- 启动代码 (核心任务之一):
- 在 main() 执行前,启动代码负责将 Flash 中 _sidata 地址开始的 .data 段初始值数据,复制到 RAM 中 _sdata 地址开始的区域
- 复制长度是 _edata - _sdata
- 复制完成后,RAM 中的这些变量就有了程序设定的初始值
关键特性:需要初始化(非零初始值),占用 Flash (存初始值) 和 RAM (存变量本身) 两种空间。启动时必须从 Flash 复制到 RAM。
3. .bss (未初始化数据段)
作用:存储未初始化或显式初始化为 0 的全局变量和静态变量。
存储位置:RAM。因为这些变量在程序开始运行时没有有效值(或要求为 0),不需要在 Flash 中存储任何初始数据(除了知道它的大小)。
实现 (关键点):
- 编译器为每个未初始化或初始化为 0 的全局/静态变量预留 RAM 空间
- 编译器将这些变量放入 .bss 段。注意:.bss 段本身在最终的程序镜像(烧写到 Flash 的 .bin/.hex 文件)中不占据任何实际字节空间!它只记录需要预留多少 RAM 并将其初始化为 0。
- 链接器脚本定义 .bss 段在 RAM 中的起始地址 (_sbss) 和结束地址 (_ebss)
- 启动代码 (核心任务之二):
- 在 main() 执行前,启动代码负责将 RAM 中从 _sbss 到 _ebss 的区域清零
- 清零长度是 _ebss - _sbss
关键特性:初始值为零(或未初始化),只占用 RAM 空间,在 Flash 中不存储实际数据(只记录大小)。启动时必须清零。
4. .rodata 段 (只读数据段)
作用:存储常量数据,程序运行期间不会被修改。
存储位置:Flash (ROM)。因为是只读的。
实现:
- 编译器将常量放入 .rodata 段
- 链接器将所有 .rodata 段合并并定位到 Flash 的某个区域(通常在 .text 段之后)
- 程序运行时直接从 Flash 中读取这些常量数据。有些单片机架构(如 Harvard)不能直接从 Flash 执行数据访问,可能需要特殊指令或临时复制到 RAM,但 .rodata 本身的目标位置是 Flash
关键特性:只读,存储在 Flash,不占用 RAM。CPU 直接(或通过特殊方式)从 Flash 读取。
5. stack(栈区)
作用:
- 存储局部变量(非静态)
- 存储函数调用时的返回地址
- 存储函数参数
- 保存函数调用前后的寄存器现场
存储位置:RAM。需要快速读写。
实现:
- 通常由链接脚本定义栈的起始地址(栈顶 _estack,通常是 RAM 的最高地址)和大小
- 启动代码 (核心任务之三):设置栈指针寄存器 (SP) 指向链接脚本定义的栈顶地址 _estack
- 运行时由 CPU 硬件自动管理(PUSH/POP 指令)。函数调用时,局部变量空间在栈上分配;函数返回时,空间自动释放
- 栈通常从高地址向低地址增长
6. heap(堆区)
作用:存储动态分配的内存(通过 malloc(), calloc(), new 等分配)
存储位置:RAM。需要动态管理。
实现:
- 链接脚本定义堆的起始地址(通常是 .bss 段结束之后 _end)和大小(_heap_end - _heap_start)
- 通常需要实现一个堆内存管理器(如 malloc/free 的实现,可能是 newlib 等 C 库提供的,或自定义的)。这个管理器负责在定义的堆区域内分配和回收内存块
- 堆通常从低地址向高地址增长(与栈相反),两者在 RAM 中间区域相遇
总结
| 段名 | 存储内容 | 主要存储位置 | 关键初始化动作(启动代码) | 占用空间类型 | 备注 |
|---|---|---|---|---|---|
| .text | 程序代码(指令) | Flash | 无(CPU直接执行) | Flash | 只读 |
| .data | 已初始化的全局/静态变量 | RAM | 从Flash复制初始值到RAM | Flash(值)+RAM(变量) | 启动时必须复制 |
| .bss | 未初始化的全局/静态变量(0) | RAM | 将RAM区域清零 | RAM | 不占Flash空间,启动时必须清零 |
| .rodata | 常量数据 | Flash | 无(程序直接从Flash读取) | Flash | 只读 |
| stack | 局部变量、返回地址、参数等 | RAM | 设置栈指针(SP) | RAM | 自动管理,向下增长,溢出危险 |
| heap | 动态分配的内存 | RAM | 初始化堆管理器(可选) | RAM | 程序员管理(malloc/free),向上增长,碎片风险 |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 zk`Blog!
