C程序.bss段内存分配释放逻辑及段大小变化问题咨询
C程序.bss段内存计算规则解析
你观测到的.bss段大小随已初始化全局变量变化的现象,不是什么“bss内存被扣除”,本质是链接器段布局规则、对齐填充复用、段大小统计逻辑共同作用的结果,不存在内存凭空减少的情况。
核心规则
- 段排布基础:
.data(存非0初始化的全局/静态变量)和.bss(存未初始化/初始化为0的全局/静态变量)在虚拟地址空间是连续排布的,.data在前,.bss紧接在.data的结束地址之后。 - 段大小统计逻辑:size命令输出的段大小,是段结束地址减去段起始地址的差值,不是段内所有用户变量自身大小的直接加总——段内为了满足变量对齐、段末尾对齐产生的填充空隙,只要落在该段的地址范围内,就会被计入该段的大小。
- 对齐规则:不同类型变量有自身的对齐要求(char 1字节对齐、int 4字节对齐,64位环境下默认最大对齐粒度为8字节);同时链接脚本要求
.bss段的末尾必须8字节对齐,方便操作系统初始化内存页。 - 空隙复用规则:链接器排布变量时不会浪费地址空间,不管是段末尾的对齐空隙,还是变量之间因为对齐产生的空隙,只要满足后续变量的对齐要求,就会把变量(包括编译器自动生成的内部符号,比如栈保护用的
completed.xxx标记)塞到空隙里,不会额外开辟空间。 - 总长度固定:你所有测试case里
text+data+bss的总大小dec都是1978,就是因为各段加起来的总长度始终对齐到链接脚本要求的对齐边界,只是填充空隙的归属段、变量排布位置变了,总内存占用没有变化。 - 粒度误区澄清:你之前观测到的“.bss以8字节为粒度增长”是特殊场景下的表象——只有当
.bss起始地址刚好8字节对齐、且没有空隙可以复用时,新增变量才会让bss大小按8字节步长涨,只要存在可复用的对齐空隙,就不会触发8字节粒度的增长。
测试场景逐例对应
- case1(两个未初始化int):没有自定义初始化全局变量时,.data段结束地址本身就是8字节对齐的。两个int型未初始化变量各占4字节,加上编译器自动生成的4字节内部bss符号,总共8字节的有效内容,算上bss末尾对齐到8字节边界需要的填充,总bss大小为16,
data+bss=544+16=560,符合8字节对齐要求。 - case2(一个int初始化、一个int未初始化):初始化的int a占4字节被放入.data段,data大小增加4变成548,此时.data的结束地址模8余4,不再是8字节对齐。.bss从这个非对齐地址开始排布,内部4字节符号+4字节未初始化int b共8字节有效内容,算上bss末尾对齐需要的4字节填充,总bss大小是12,
data+bss=548+12=560,和case1总长度完全一致。你看到的bss减少4字节,本质是原本算在bss里的对齐填充,因为data段变长,归属到了段间公共填充区域,不是真的被扣除。 - case3(char初始化、int未初始化):初始化的char a占1字节,data大小增加1变成545,此时.data结束地址模8余1。你贴的
nm -n输出也能印证排布逻辑:bss段起始标记__bss_start紧接在a之后,首先要满足第一个4字节对齐内部符号的要求,开头空出3字节填充,再放4字节内部符号、4字节int b,最后算上末尾对齐填充,总bss大小为12,和输出一致。 - case4(int初始化、char未初始化):初始化的int a占4字节,data大小变成548,此时.data末尾距离下一个8字节对齐点正好有4字节空隙,链接器直接把原本放在bss里的4字节内部符号塞到了这个空隙里,不需要再占bss空间。bss里只剩1字节的未初始化char b,算上对齐填充总大小为4,总段长依然符合对齐要求。
- case5(char初始化、char未初始化):初始化的char a占1字节,data大小变成545,.data末尾距离下一个8字节对齐点有7字节空隙,足够塞下4字节内部符号+1字节未初始化char b,剩下的空隙全算在bss段的填充里,所以bss大小正好是7,和输出完全匹配。
注意:C标准没有规定全局变量的排布顺序,不同GCC版本、链接脚本、编译选项(比如栈保护开关、PIE开关、优化等级)都会改变变量排布位置,你观测到的具体数值是当前编译环境下的结果,上述核心规则是通用的。
内容的提问来源于stack exchange,提问作者aTechieSmile
相关产品推荐
相关产品推荐

