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. 面试题:先给结论与边界](#1. 面试题:先给结论与边界)
  • [2. 进程虚拟地址里常见「段」在说什么](#2. 进程虚拟地址里常见「段」在说什么)
  • [3. .data 与 .bss:谁占磁盘、谁运行时清零](#3. .data 与 .bss:谁占磁盘、谁运行时清零)
  • [4. .rodata:const 全局为何常在这里](#4. .rodata:const 全局为何常在这里)
  • [5. 动手验证:nm 符号类型速查](#5. 动手验证:nm 符号类型速查)
  • [6. readelf:看段地址与顺序](#6. readelf:看段地址与顺序)
  • [7. 速查表与常见误区](#7. 速查表与常见误区)
  • [8. 延伸阅读与免责声明](#8. 延伸阅读与免责声明)

1. 面试题:先给结论与边界

问题 典型答案(ELF + g++/Clang,Linux x86_64 常见)
const int g = 10;(文件域) 多在 .rodata不可写nm 常为 r
int g = 10; .datanm 常为 D (全局)或 dstatic 文件域)。
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 习惯 遇到「符号找不到 / 多重定义 / 段异常」时,会不会用 nmobjdump -treadelf 对照 TU(翻译单元)VMA,而不是只盯着源码猜。

2. 进程虚拟地址里常见「段」在说什么

下面为 教科书式示意 (真实地址与是否合并 PIEASLR 、链接脚本有关),表达的是 相对顺序、权限分工 与后文 「.text 与 .rodata 常同映射为只读」 的呼应。权限列为进程视角常见简写(R ead / W rite / eX ecute;- 表示无)。

text 复制代码
  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" 的存储体,通常也在 .rodatachar* 指向它时,改 p[0] 常崩溃,即此类权限问题)。

C++ 链接 :文件域 const int x = 1; 默认 内部链接 (等价于 static const 的文件内可见性),nm 里常出现 _ZL... 风格 的修饰名;若需要跨翻译单元共享,通常用 extern const int x; 在某处定义------符号形态与是否仍进 .rodata 需以实际 nm 为准。


5. 动手验证:nm 符号类型速查

5.1 示例源码

cpp 复制代码
// segdemo.cpp --- 建议用 g++ -O0 -g 编译便于对照
const int a = 10;
const int b = 0;
int c;
int d = 9;
static int e;
static int f = 10;

int main() { return a + b + c + d + e + f; }

5.2 命令

bash 复制代码
g++ -O0 -g segdemo.cpp -o segdemo
nm -C --defined-only segdemo | sort

5.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 一带为 rd/fD/dc/eB/b (具体符号名是否被修饰取决于 C++ ABI 与是否 extern "C")。


6. readelf:看段地址与顺序

bash 复制代码
readelf -S segdemo

关注 .text.rodata.data.bssVMAAlign ;常见现象是 .rodata VMA 紧挨或靠近 .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 零初值 可写 全局才典型进 .bssconst 只读 常在 .rodata
nm 大小写只是大小写」 在 GNU nm 里常区分 全局(Global)可见局部(Local)/ 文件内 static符号绑定属性以手册为准
「所有平台都一样」 Windows PE 中类似只读常量区常用 .rdata 等节名表达;嵌入式裸机不同链接脚本 与 ELF 也不尽相同;本文以 Linux ELF 为主。

陷阱代码(UB,勿依赖「是否崩溃」当逻辑) :通过 const_cast 或 C 风格强转 去掉 const 再写入,若对象实际位于 只读映射 ,在 Linux 上常见 Segmentation fault ;即便未立刻崩溃,仍是 C++ 未定义行为

cpp 复制代码
const int g_const = 10;

int main() {
    int* p = const_cast<int*>(&g_const);  // 仍不保证可写
    *p = 20;  // Undefined Behavior
    return 0;
}

实际现象以 页权限、编译器是否把常量完全优化掉 为准;教学上可用 readelf -l 看 LOAD 段 RWEgdb/catch syscall 对照,但结论应写 UB,不要写「一定崩 / 一定不崩」。

8. 延伸阅读与免责声明

检索线索 用途
man nm / man readelf 符号字母与段表字段权威说明。
ELFLinkers and Loaders 段、节、加载与权限。
GCC/Clang -fdata-sections、LTO 可能改变合并与段布局时的对照方法。
objdump -h / -t readelf 互补看节名与符号表。

免责声明 :段布局、符号名修饰与 const 合并 行为随 编译器版本、优化、语言标准模式 变化;面试回答建议句式为「在 Linux ELF + g++/Clang 的典型配置下,我会用 nm/readelf 验证为... 」,避免绝对化。上文 §1 已归纳面试官常见考察点,可与本节工具链 disclaimer 一并使用。


记住:段名是工具链与 OS 加载约定的结果;会查 nm/readelf 比背「标准答案」更经得起追问。

相关推荐
kobesdu2 小时前
【ROS2实战笔记-19】ROS2 生命周期节点的启动顺序、状态转换陷阱与热备方案
java·前端·笔记·机器人·ros·ros2
neo_Ggx233 小时前
Maven 版本管理详解:SNAPSHOT、Release 与 Nexus 仓库的区别和影响
java·maven
matlabgoodboy3 小时前
软件开发定制小程序APP帮代做java代码代编写C语言设计python编程
java·c语言·小程序
江离w3 小时前
新版vibecoding项目初始化指令
java
tongluowan0073 小时前
Spring MVC 底层工作流程+源码分析
java·spring·mvc
王老师青少年编程3 小时前
csp信奥赛C++高频考点专项训练之字符串 --【字符串排序】:合并序列
c++·字符串·csp·高频考点·信奥赛·字符串排序·合并序列
java1234_小锋4 小时前
SpringBoot为什么要禁止循环依赖?
java·数据库·spring boot
handler014 小时前
UDP协议与网络通信知识点
c语言·网络·c++·笔记·网络协议·udp
折哥的程序人生 · 物流技术专研4 小时前
《Java 100 天进阶之路》第17篇:Java常用包装类与自动装箱拆箱深入
java·开发语言·后端·面试