程序为什么要分段?.text / .data / .bss 到底有什么区别?

面试经常会问:c语言程序有哪些分段?.text.data.bss 到底有什么区别?很多人能背出定义,但不一定理解“为什么必须这样设计”。

要把这个问题讲透,先引入两个非常关键的概念:加载地址(LMA)*和*运行地址(VMA)

加载地址(Load / LMA):固件镜像“存放”的位置。对 MCU 来说,通常就是 Flash 里的地址(例如 STM32 的 0x0800_0000 开始)。 程序分段

运行地址(Run / VMA):CPU 实际“访问/执行”时使用的地址,也就是链接时为各段安排的运行位置(比如 .data/.bss 常在 RAM)。

以 STM32F 系列为例:程序下载时烧到 Flash;运行时代码通常仍然在 Flash 中 XIP(就地执行),而可读写数据会被放到 RAM。于是 .data 很典型:LMA 在 Flash,VMA 在 RAM

下面用 6 个最经典的区域把它讲清楚:.text.rodata.data.bss、堆、栈。 程序分段

1).text 段:代码放哪里、为什么通常能在 Flash 直接跑?

.text 段存放指令代码,通常是只读的。对 MCU 来说,.text 一般链接到 Flash,CPU 直接从 Flash 取指执行(XIP),不需要搬到 RAM(除非你为了速度做“代码搬运到 RAM 执行”)

2).rodata / .data / .bss:同样是数据,为什么要分三类?

.rodata.data.bss 都与“数据”有关,但它们的初始值是否需要占用镜像空间不同。 程序分段

int value;          // 未初始化(默认 0)
int value0 = 0;     // 初始化为 0
int value1 = 666;   // 初始化为非 0

2.1 .data:有“非 0 初值”的全局/静态变量

  • .data 段存放:初始化为非 0 的全局变量/静态变量。 程序分段

  • 为什么要有 .data:因为这些变量的初值(比如 666)必须被保存下来,否则上电后 RAM 里是随机值。

  • 所以 .data 的典型形态是:

    • LMA 在 Flash:Flash 里存一份“初值镜像”

    • VMA 在 RAM:程序运行时读写的是 RAM 那份

启动时会发生:把 Flash 中的 .data 初值拷贝到 RAM 的 .data 运行地址

2.2 .bss:未初始化/初始化为 0 的全局/静态变量(不占镜像空间)

  • .bss 段存放:未初始化或者初始化为 0的全局变量/静态变量。 程序分段

  • .bss 的关键特点:不占用实际固件镜像空间(ELF 里通常是 NOBITS),镜像里只需要记录“范围/大小”。

  • 启动时会发生:把 RAM 中 .bss 对应范围清零

这时候就有人会问了,主包主包,flash里的数据是怎么移到ram中的呢?

那么好,看一段stm32f1的启动代码,可以看到复位中断后进入的是__main这个就是编译器帮我们自动生成的一个函数,里面会自动进行copy data段以及bss段清零的操作,之后再跳转到自己编写main函数中。

2.3 .rodata:只读常量(const / 字符串常量等)

.rodata 通常存放只读常量(例如 const 全局常量、字符串常量等)。它的加载地址和运行地址通常都在 Flash,从物理上保证“只读”。

3)栈(Stack):自动、快,但溢出很致命

栈是自动管理的、向下增长的临时空间,主要用于:局部变量、函数调用现场保存、中断入栈等。进入/退出函数会自动分配/回收,不需要手动管理。static 修饰的局部变量不在栈上,而在 .data/.bss 这类静态存储区。 程序分段

栈溢出常见原因:递归过深、局部变量过大、(RTOS 下)任务栈配得太小。溢出后常表现为 HardFault/跑飞

5)堆(Heap):灵活,但 MCU 上要谨慎

堆是动态分配的内存(malloc/free),一般向上增长。它更灵活,但在 MCU 上常见问题是:碎片化、实时性差、泄漏难排查。 程序分段

  • 内存溢出:一次 malloc 过大或长期分配导致超过可用堆空间。 程序分段

  • 内存泄漏:分配后没有及时 free,可用堆逐渐被吃光。

在小ram的MCU中尽量使用全局变量,避免用堆可能导致的内存问题。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐