全局变量存于.data或.bss段:未初始化或显式初始化为0的进.bss(磁盘不占空间,运行时零初始化);显式初始化为非零值的进.data(磁盘存初始值,加载时复制)。

全局变量到底存在哪儿?.data 和 .bss 的区别不是“有没有初始化”那么简单
全局变量的存储位置由链接器决定,不是编译器拍板。未初始化或初始化为 0 的全局变量(比如 int g_a;、static char buf[1024] = {};)进 .bss 段;显式初始化为非零值的(比如 int g_b = 42;)进 .data 段。关键在于:这两段在磁盘上不占实际空间(.bss 完全不存内容,.data 只存初始值),但加载到内存后都会映射为可读写数据页。
-
.bss段在 ELF 文件里只记录大小,运行时由 loader 零初始化 —— 所以它节省磁盘空间,但首次写入仍会触发缺页中断 -
.data段把初始值直接写进二进制文件,加载时 memcpy 进内存 —— 启动稍慢一点,但避免了首次写入的页错误开销 - 用
size命令看.bss大小:输出里bss列就是它;readelf -S能确认段标志(.bss是ALLOC + WRITE,但LOAD为否)
为什么 static int x = 0; 在 .bss,而 static int y = 1; 却在 .data?
这不是 C++ 标准规定的“行为”,而是 ABI 和工具链的约定。标准只说“静态存储期对象零初始化”,没规定怎么存。GCC/Clang 默认遵守 System V ABI:所有“定义时求值为 0”的变量归 .bss,哪怕写了 = 0。但一旦初始化表达式含非常量(比如 = sizeof(int) 或内联 constexpr 函数调用),就可能被放进 .data —— 因为链接器无法在链接时确定值。
- 容易踩的坑:
static char big_buf[1 看似初始化了,其实等价于未初始化,进 <code>.bss;但若写成= {1},整个 1MB 就打进.data,二进制瞬间膨胀 - 调试时用
objdump -t查符号所在段:.bss符号的 section 列显示.bss,且 size 是十进制数(不是十六进制地址) - 注意
const全局变量:如果类型是 POD 且初始化为常量表达式,通常进.rodata,不属于.data/.bss
extern 声明的全局变量不会分配存储,但链接失败往往卡在这儿
extern int g_flag; 本身不占内存,只是告诉编译器“这玩意儿在别处定义”。真正分配存储的是那个唯一的定义(比如 int g_flag = 1;)。如果漏掉定义,或者定义在某个被 strip 掉的 object 文件里,链接时报 undefined reference to 'g_flag' —— 此时查 .data/.bss 没用,得用 nm -C 挨个检查目标文件有没有 T(代码)、D(data)、B(bss)标记的符号。
- 常见误操作:头文件里写
extern int log_level = 3;—— 这其实是定义(带初始化的extern是定义),会导致多个 TU 都生成.data条目,链接时报重复定义 - 跨模块共享全局变量时,确保只有一个源文件包含定义,其余只用
extern声明;C++ 内联变量(inline int g_cnt = 0;)可绕过此限制,但仍是.data存储 -
__attribute__((section(".mydata")))可强制变量进自定义段,但需同步修改链接脚本,否则运行时可能段不可写或地址冲突
内存布局里 .data/.bss 紧挨着堆,但别指望用指针算偏移来“偷空间”
典型进程内存布局中,.data 和 .bss 在低地址端,堆(brk)紧贴其后向上增长。理论上,&_end(.bss 结束地址)和 sbrk(0) 初始值很接近。但这是实现细节,不是标准保证。
- 不能依赖
_end地址做内存管理:ASLR 可能随机化整个数据段基址;PIE 编译下整个程序地址都变;某些嵌入式 linker script 甚至把.bss搬到 RAM 末尾 - 想动态扩展全局缓冲区?用
mmap(MAP_ANONYMOUS | MAP_PRIVATE),别试图sbrk()或改.bss大小 —— 后者需要重写 ELF header,运行时几乎不可能 - 查看真实布局:Linux 下
/proc/self/maps(运行时)或readelf -l(加载视图),注意PT_LOADsegment 是否把.data和.bss合并在同一可写页中
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










