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; .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 常同映射为只读」 的呼应。权限列为进程视角常见简写(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" 的存储体,通常也在 .rodata (char* 指向它时,改 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 一带为 r ,d/f 为 D/d ,c/e 为 B/b (具体符号名是否被修饰取决于 C++ ABI 与是否 extern "C")。


6. readelf:看段地址与顺序

bash 复制代码
readelf -S segdemo

关注 .text、.rodata、.data、.bss 的 VMA 与 Align ;常见现象是 .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」 零初值 可写 全局才典型进 .bss;const 只读 常在 .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 段 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 比背「标准答案」更经得起追问。

相关推荐
paopaokaka_luck4 小时前
滑雪场管理系统(AI智能助手流式对话,ECharts 运营数据分析、教练、雪具与场地一体化预约,钱包支付退款、订单核销与服务评价)
java·人工智能·spring boot·网络协议·数据分析·echarts
Wang's Blog4 小时前
Java 项目实战: 交友项目 - Dubbo 的内部结构与一次调用的完整链路
java·dubbo
Wang's Blog4 小时前
Java 项目实战: 交友项目 - Dubbo 入门引导
java·dubbo·交友
蜗牛互联网5 小时前
Python Agent工具调用的白名单、参数校验与审批门禁
java·服务器·前端·人工智能·后端·python
SL_staff5 小时前
当规则引擎开始‘懂业务’:JVS-Rules金融风控四大原生能力拆解
java·python·全栈
Shaoshing5 小时前
前缀和与差分
java·数据结构·算法·前缀和·差分
dd聊技术5 小时前
Prompt 从代码常量搬进数据库:改一句话,不用再发一次版
java·后端
ly76896 小时前
Spring Data MongoDB 批量写入实践:BulkOperations 批大小与写关注权衡
java·mongodb·spring·批量写入·bulkoperations·写关注·mongotemplate
TAKETODAY6 小时前
Netty 是怎么把 HTTP 请求交给 MVC 的?一个请求的完整链路
java·netty
蚰蜒螟6 小时前
从内核启动到 Java NIO:一次 socket accept 的源码串联之旅
java·python·nio