引言
第一次接触到这个问题是在刚刚接触进程时 看到这条概念不禁非常迷惑 虽然当时查了些资料有了一些了解 但还是马马虎虎,知道学习了操作系统才对这条概念有了更深的认识 遂记录一篇博客
我们知道我们所写的代码在进过编译器和汇编器以后已经成了一个二进制文件,术语叫做可重定位目标文件ELF(Executable and Linkable Format),它的组成包括一个ELF头与很多的节
像图片中的 .text , .data, ,rodata 等其实都是节 (section) ,节在我的理解其实就是一些不同数据的集合,它们的信息被储存在文件最开始的ELF头的位置 如果你想动手试试 可以对一个文本文件(C程序)执行以下操作来查看ELF头 已加深理解
gcc -E test.c -o test.i
gcc -S test.i -o test.s
gcc -c test.s -o test.o
objdump -h test.o
为什么要解释何为节呢 因为我们问题的答案就藏在bss节,其实刚开始学习这一块的内容时很容易可以查到 data 与 bss 的用处
- bss 未初始化的全局变量和静态变量以及初始化为零的全局变量和静态变量
- data 已初始化且不为零的全局变量与静态变量
我们可以发现两者之间唯一的区别就是值是否为零(未初始化的全局变量和未初始化的静态变量值均为零) 这就有点意思了,为什么这么小的区别却要如此大费精力的把它们分成两个节呢?
原因是因为内存初始化的时候bss段映射的是匿名文件 其内容为填充的二进制零 也就是说其本身没有值 但data段映射的则是数据文件 其本身是有值的 所以为了节省内存(划重点),我们不对bss段进行储存 这就是问题的答案
但有一个问题来了 就是不存储怎么知道有这么一个变量呢 这就引入了一个 “符号” 的概念 所有的全局变量 静态变量(包括函数)其都可以看做一个符号 在汇编后产生一个符号表(参数加上-g则还有一张调试符号表 想想gdb的过程) 所有的非局部变量都在其中站一个符号位 (所以说还是存储的) 在链接阶段进行符号解析和重定位 把每一个符号与运行时地址相对应 这其中并不涉及对内存的映射 在可执行文件期间才进行,也就是需要值的时候在可执行文件执行期间才需要 而我们已经把没有存储的值放入bss 在映射期间为匿名文件 这样当然在程序执行阶段就可以顺利的进行了 至此 我们的问题算是彻底解决了
可以再打印一个ELF头来加深印象
我们可以看到第二个Load中filesize段 也就是可执行文件中段的大小 十六进制表示为0x288 而在内存映像中却是0x298 这就又印证了bss段在目标文件中不分配存储空间
再提一个比较有意思的东西 bss段的由来是什么呢 其实最准确的应该是Block Storage Start(块存储开始),但这样你可能会更方便记忆Best Save Space
毕竟名字是方便人的嘛
参考资料:
- Computer Systems:A Programmer’s Perspective
- Linux C 编程实战