Appearance
.text / .rodata / .data / .bss —— 代码与数据四节
这四个 Section 是每个 ELF 二进制里最基础的部分——
.text放你的代码、.rodata放只读常量、.data放已初始化的全局变量、.bss放未初始化的全局变量。size命令的输出、readelf -S里最先被关心的几行、链接脚本里的SECTIONS块,核心都在操作这四节。本篇把这四节的类型、标志、加载行为、以及最常见的.datavs.bss区别一次性讲透。
更新时间:2026-08-06
一、术语前置
| 缩写/术语 | 全称 | 含义 |
|---|---|---|
| PROGBITS | Program Bits | 节在文件里有实际内容(.text / .rodata / .data) |
| NOBITS | No Bits | 节在文件里不占空间,只在内存里占(.bss) |
sh_flags | Section Flags | 节的属性位:A=alloc 加载、W=writable 可写、X=executable 可执行 |
sh_addr | Section Address | 运行时虚拟地址;为 0 表示运行时不加载 |
sh_size | Section Size | 节大小 |
sh_offset | Section Offset | 在文件里的偏移 |
二、四节全景图

这四节都被打包进 LOAD 段进内存:
| 节 | 所在的 LOAD 段 | 权限 | Type |
|---|---|---|---|
.text | LOAD #1(R+X) | 只读可执行 | PROGBITS |
.rodata | LOAD #1(R+X) | 只读 | PROGBITS |
.data | LOAD #2(R+W) | 可读写 | PROGBITS |
.bss | LOAD #2(R+W) | 可读写 | NOBITS |
三、.text —— 代码段
存放编译后的机器指令。一个 ELF 文件里所有的函数体(.o 里的 .text 被链接器合并),最终都在这里。
bash
$ readelf -S ./cpu_demo | grep text
[13] .text PROGBITS 00000000004019a0 000019a0
00002362 000000 AX 0 0 16| 字段 | 值 | 含义 |
|---|---|---|
Address | 0x4019a0 | 运行时入口(这个例子恰好就是 ELF Header 的 Entry point,_start 在这) |
Size | 0x2362(9058 字节) | 所有函数的机器码总大小 |
Flags | AX | Alloc + Execute(可执行,不可写) |
Align | 16 | 16 字节对齐 |
.text 被 -O0/-O2/-Os 直接影响——优化选项改变的是这一节的体积和指令排列。
四、.rodata —— 只读数据段
存放运行期不可改的数据:字符串常量、const 修饰的全局量、虚函数表(vtable)、跳转表(switch 的 jump table)。
c
const char* msg = "hello"; // 指针 msg 在 .data,"hello" 字符串在 .rodata
const int SIZE = 4096; // 在 .rodata(若编译器不常量折叠)
static const int TBL[] = {1,2,3}; // 在 .rodata写
.rodata会触发 SIGSEGV(段错误)——因为它的 LOAD 段权限是 R 不是 W。
五、.data —— 已初始化数据段
存放有非零初始值的全局变量和静态局部变量。占文件体积——初始值直接嵌入 ELF 文件。
c
int global = 42; // 在 .data,文件里存 0x0000002a
int zero = 0; // 可能在 .bss(编译器优化:初值=0 从 .data 迁到 .bss)
static int st = 100; // 在 .databash
$ readelf -S ./cpu_demo | grep '\.data'
[25] .data PROGBITS 00000000004081c8 000081c8
00000004 WA 0 0 8Size=0x4(4 字节),文件里 Offset=0x81c8 处确实存了这 4 字节数据。Flags=WA——可写但不可执行(W^X 安全策略)。
六、.bss —— 未初始化数据段(最关键的一节)
6.1 概念
存放未初始化或初值为 0 的全局/静态变量。Type=NOBITS——文件里 0 字节,只在 Section Header 里记一个 Size。运行时 LOAD 段 MemSiz > FileSiz 的差额就是 .bss,内核加载时在内存里补零。
c
int big_buf[1000000]; // 在 .bss,文件不膨胀,运行期占 4MB 内存
static int counter; // 在 .bss6.2 .data vs .bss 对照
| 维度 | .data | .bss |
|---|---|---|
| 存放 | 有非零初值的全局/静态变量 | 未初始化 / 初值为 0 的全局/静态变量 |
| Section Type | PROGBITS | NOBITS |
| 占文件体积? | 是(初始值写入文件) | 否(只记大小) |
| 占内存? | 是 | 是(加载时清零) |
Flags | WA | WA |
| LOAD 段中的表现 | LOAD #2 的 FileSiz 覆盖 | MemSiz - FileSiz 的差额 |
6.3 经典验证
bash
$ size ./cpu_demo
text data bss dec hex filename
9058 580 1072 10710 29d6 cpu_demo
# bss = 1072 字节,不占文件体积
# 若在 .c 里加 int arr[1000000]; bss 暴涨 4MB,但文件大小几乎不变这就是为什么声明一个大数组不会让可执行文件变大——它进 .bss,只记长度。
七、size 命令速查
bash
size ./cpu_demo # text / data / bss 三段
size -A ./cpu_demo # SysV 格式(详细,含 dec/hex)
size -B ./cpu_demo # BSD 格式
objdump -h ./cpu_demo | grep -E '\.(text|data|bss|rodata)'八、与相关文档的关系
| 文档 | 覆盖内容 | 本文的关系 |
|---|---|---|
| elf-format.md §四 | 常见 Section 简要列表 | 本文是四节的专题深入 |
| symbol-table.md | 符号表与 DWARF | 全局变量符号的 st_shndx 指向本文的 .data/.bss |
| compile-link-load.md | 链接时各 .o 的 .text/.data/.bss 合并 | 本文解释"合并的是什么" |
| memory-layout.md | 进程内存布局 | 本文的节最终映射到内存布局的哪一段 |
一句话总结:
.text(AX,你的代码)、.rodata(A,常量)、.data(WA,已初始化全局量)、.bss(WA,NOBITS,有尺寸没体积,加载时清零)——.bss的NOBITS属性是"声明大数组不膨胀二进制"的根本原因。