建议在阅读本文章之前先阅读以下文章:
程序的编译、链接过程

前置知识

  1. Flash (ROM/Program Memory):存储程序代码和常量。
  2. RAM (Data Memory):存储运行时的变量(全局、静态、局部)、堆栈数据。
  3. 链接器脚本 (Linker Script, .ld 文件)
    告诉链接器如何将汇编器生成的各个目标文件中的“段”组合起来,最终映射到物理的 Flash 和 RAM 地址空间。
  4. 启动代码 (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 中)
  • 链接脚本做两件重要的事情:
    1. 定义 .data 段在 RAM 中的运行时起始地址 (_sdata) 和结束地址 (_edata)
    2. 定义 .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),向上增长,碎片风险