C++变量存储与ELF段布局详解 从const全局到rodata与nm_readelf验证实践
C++变量存储与ELF段布局详解_从const全局到rodata与nm_readelf验证实践
一道常见面试题:const全局变量落在.data还是.bss?若只背「非零进.data、零进.bss」,容易忽略只读语义与段权限:在典型Linux ELF + GCC/Clang下,文件作用域的const已初始化整型常量往往进.rodata(只读数据段),由加载器映射为不可写页,与.data/.bss的可读写页分离。本文从段语义讲到nm/readelf自证,并交代C++ 链接属性、编译器差异与局部const的常见落点,便于面试与排障对照。
目录
- 1. 面试题:先给结论与边界
- 2. 进程虚拟地址里常见「段」在说什么
- 3. .data 与 .bss:谁占磁盘、谁运行时清零
- 4. .rodata:const 全局为何常在这里
- 5. 动手验证:nm 符号类型速查
- 6. readelf:看段地址与顺序
- 7. 速查表与常见误区
- 8. 延伸阅读与免责声明
1. 面试题:先给结论与边界
| 问题 | 典型答案(ELF + g++/Clang,Linux x86_64 常见) |
|---|---|
const int g = 10;(文件域) | 多在.rodata,不可写;nm常为r。 |
int g = 10; | .data,nm常为D(全局)或d(static文件域)。 |
int g;或int g = 0; | 多在.bss(零或未初始化由运行时清零),nm常为B/b。 |
边界:具体是否完全进.rodata、是否与其他常量合并、是否mergeable,随优化级别(-O2)、是否取址、是否extern const跨 TU等变化;以本机nm/readelf与编译器文档为准。下文示例默认-O0,便于对照符号。
面试官常通过这个题看什么
| 考察点 | 说明 |
|---|---|
| 不止语法糖 | 能否把const与存储期、链接、段权限联系起来,而不是只背「常量不能改」。 |
| UB 与信号 | 是否知道强转去掉const再写是未定义行为(UB);在 Linux 上若对象真在只读页,常见表现是SIGSEGV,与「改没改成」无关。 |
| Debugging 习惯 | 遇到「符号找不到 / 多重定义 / 段异常」时,会不会用nm、objdump -t、readelf对照TU(翻译单元)与VMA,而不是只盯着源码猜。 |
2. 进程虚拟地址里常见「段」在说什么
下面为教科书式示意(真实地址与是否合并PIE、ASLR、链接脚本有关),表达的是相对顺序、权限分工与后文「.text 与 .rodata 常同映射为只读」的呼应。权限列为进程视角常见简写(Read /Write / eXecute;-表示无)。
High Address +------------------+ Permissions 说明 | Stack | RW- 局部自动变量、调用帧 +------------------+ | ↑ | | heap growth | +------------------+ | Heap | RW- malloc / new +------------------+ | .bss | RW- 运行时清零的可写全局/静态 +------------------+ | .data | RW- 带非零初值映像的可写全局/静态 +------------------+ | .rodata | R-- <--- const 全局、字符串字面量等(只读) +------------------+ | .text | R-X 机器码(一般不可写、可执行) Low Address要点:.text与.rodata常为非可写映射,便于页权限隔离与TLB行为;.data/.bss为可读写。.rodata与.text在 VMA 上常相邻,便于操作系统用同一类只读(及代码段的 RX)策略管理相邻页。
3. .data 与 .bss:谁占磁盘、谁运行时清零
| 段 | 典型内容 | 可执行文件里 |
|---|---|---|
.data | 已初始化且在映像里要占位的非零初值 | 占磁盘,加载时拷入 RW 页 |
.bss | 未初始化或全零初值的可写全局/静态 | 常不占磁盘字节(NOBITS),只在内存占位,由加载/启动路径清零 |
直觉:巨大全零数组若硬塞进.data,会把 ELF 撑胖;放.bss只记录大小更省镜像体积。
4. .rodata:const 全局为何常在这里
- 语义:
const对象不应通过合法 C++ 语义被改写;放进可写.data会与「只读」目标冲突(仍可能通过未定义行为改内存,但不应被映射策略鼓励)。 - 实现:编译器把「编译期已知、只读」数据放进
.rodata,映射为RO,越界写易SIGSEGV。 - 字符串字面量:如
"hello"的存储体,通常也在.rodata(char*指向它时,改p[0]常崩溃,即此类权限问题)。
C++ 链接:文件域const int x = 1;默认内部链接(等价于static const的文件内可见性),nm里常出现_ZL...风格的修饰名;若需要跨翻译单元共享,通常用extern const int x;在某处定义——符号形态与是否仍进.rodata需以实际nm为准。
5. 动手验证:nm 符号类型速查
5.1 示例源码
// segdemo.cpp — 建议用 g++ -O0 -g 编译便于对照constinta=10;constintb=0;intc;intd=9;staticinte;staticintf=10;intmain(){returna+b+c+d+e+f;}5.2 命令
g++-O0-gsegdemo.cpp-osegdemo nm-C--defined-only segdemo|sort5.3 如何读第二列类型(常见子集)
nm字母 | 常见含义 | 常与哪类段对应 |
|---|---|---|
r | read-only data | .rodata |
D/d | 已初始化 data object | .data(大写/小写与全局 vs static可见性相关,依nm手册) |
B/b | BSS | .bss |
T/t | text(代码) | .text |
你应能在输出里看到a/b一带为r,d/f为D/d,c/e为B/b(具体符号名是否被修饰取决于C++ ABI与是否extern "C")。
6. readelf:看段地址与顺序
readelf-Ssegdemo关注.text、.rodata、.data、.bss的VMA与Align;常见现象是.rodataVMA 紧挨或靠近.text,而.data/.bss落在更高 VMA 区域(与链接脚本、PIE 有关)。这支持「代码与只读数据共享只读映射」的工程叙述。
发布或内部分享时,可附一张本机终端readelf -S segdemo的截图(高亮上述四段),读者对VMA 顺序一眼更稳。
7. 速查表与常见误区
7.1 速查(文件域 / 静态存储期,Linux ELF 常见)
| 写法 | 常见段 | nm线索 |
|---|---|---|
const int x = k; | .rodata | r |
int x = 非零; | .data | D/d |
int x;/int x = 0; | .bss | B/b |
| 字符串字面量 | .rodata | 常表现为r或与合并常量相邻 |
函数内const int y = 3; | 多为栈上常量(或优化进立即数),不与全局.rodata混谈 |
7.2 误区与陷阱示例
| 误区 | 更正 |
|---|---|
「const int g = 0一定在.bss」 | 零初值可写全局才典型进.bss;const只读常在.rodata。 |
「nm大小写只是大小写」 | 在 GNUnm里常区分全局(Global)可见与局部(Local)/ 文件内 static等符号绑定属性,以手册为准。 |
| 「所有平台都一样」 | Windows PE中类似只读常量区常用.rdata等节名表达;嵌入式裸机、不同链接脚本与 ELF 也不尽相同;本文以Linux ELF为主。 |
陷阱代码(UB,勿依赖「是否崩溃」当逻辑):通过const_cast或 C 风格强转去掉const再写入,若对象实际位于只读映射,在 Linux 上常见Segmentation fault;即便未立刻崩溃,仍是C++ 未定义行为。
constintg_const=10;intmain(){int*p=const_cast<int*>(&g_const);// 仍不保证可写*p=20;// Undefined Behaviorreturn0;}实际现象以页权限、编译器是否把常量完全优化掉为准;教学上可用readelf -l看 LOAD 段 RWE与gdb/catch syscall对照,但结论应写UB,不要写「一定崩 / 一定不崩」。
8. 延伸阅读与免责声明
| 检索线索 | 用途 |
|---|---|
man nm/man readelf | 符号字母与段表字段权威说明。 |
| ELF与Linkers and Loaders | 段、节、加载与权限。 |
GCC/Clang-fdata-sections、LTO | 可能改变合并与段布局时的对照方法。 |
objdump -h/-t | 与readelf互补看节名与符号表。 |
免责声明:段布局、符号名修饰与const 合并行为随编译器版本、优化、语言标准模式变化;面试回答建议句式为「在 Linux ELF + g++/Clang 的典型配置下,我会用nm/readelf验证为…」,避免绝对化。上文§1已归纳面试官常见考察点,可与本节工具链 disclaimer 一并使用。
记住:段名是工具链与 OS 加载约定的结果;会查nm/readelf比背「标准答案」更经得起追问。
