为啥需要 .bss 段?

Posted

技术标签:

【中文标题】为啥需要 .bss 段?【英文标题】:Why is the .bss segment required?为什么需要 .bss 段? 【发布时间】:2012-03-21 01:43:03 【问题描述】:

我所知道的是,全局变量和静态变量存储在.data 段中,未初始化的数据在.bss 段中。我不明白的是为什么我们有未初始化变量的专用段?如果未初始化的变量在运行时分配了值,该变量是否仍仅存在于 .bss 段中?

在下面的程序中,a.data段中,b.bss段中;那是对的吗?如果我的理解有误,请纠正我。

#include <stdio.h>
#include <stdlib.h>

int a[10] =  1, 2, 3, 4, 5, 6, 7, 8, 9;
int b[20]; /* Uninitialized, so in the .bss and will not occupy space for 20 * sizeof (int) */

int main ()

   ;
  

另外,请考虑以下程序,

#include <stdio.h>
#include <stdlib.h>
int var[10];  /* Uninitialized so in .bss */
int main ()

   var[0] = 20  /* **Initialized, where this 'var' will be ?** */

【问题讨论】:

您可以将 BSS 理解为 更好地节省空间 【参考方案1】:

原因是为了减小程序大小。想象一下,您的 C 程序在嵌入式系统上运行,其中代码和所有常量都保存在真正的 ROM(闪存)中。在此类系统中,在调用 main() 之前,必须执行初始“向下复制”以设置所有静态存储持续时间对象。它通常会像这样伪:

for(i=0; i<all_explicitly_initialized_objects; i++)

  .data[i] = init_value[i];


memset(.bss, 
       0, 
       all_implicitly_initialized_objects);

其中 .data 和 .bss 存储在 RAM 中,但 init_value 存储在 ROM 中。如果它是一个段,那么 ROM 必须用很多零填充,显着增加 ROM 大小。

基于 RAM 的可执行文件的工作方式类似,当然它们没有真正的 ROM。

另外,memset 可能是一些非常高效的内联汇编器,这意味着启动时向下复制可以更快地执行。

【讨论】:

澄清一下:.data 和 .bss 之间的唯一区别是在启动时,“copy-down”可以按顺序运行,因此速度更快。如果它没有被分成两部分,那么初始化将不得不跳过属于未初始化变量的 RAM 点,所以浪费时间。 感谢您对启动过程的解释,但是当.bss 中的变量被初始化时会发生什么?它会覆盖 0 并保留在 .bss 吗?它是否从 .bss 中删除并写入 .data(从而缩短了 .bss 段)?【参考方案2】:

.bss 段是一种优化。整个 .bss 段由一个数字描述,可能是 4 个字节或 8 个字节,它给出了它在运行进程中的大小,而 .data 部分与初始化变量的大小之和一样大。因此,.bss 使可执行文件更小,加载更快。否则,变量可能在.data 段中,显式初始化为零;该程序将很难区分。 (具体来说,.bss 中的对象地址可能与.data 段中的地址不同。)

在第一个程序中,a 将在.data 段中,b 将在可执行文件的.bss 段中。一旦程序被加载,区别就变得无关紧要了。在运行时,b 占用 20 * sizeof(int) 字节。

在第二个程序中,var 分配了空间,main() 中的赋值修改了该空间。碰巧var 的空间是在.bss 段而不是.data 段中描述的,但这并不影响程序在运行时的行为方式。

【讨论】:

例如,考虑有许多长度为 4096 字节的未初始化缓冲区。您是否希望所有这些 4k 缓冲区都对二进制文件的大小有所贡献?那会浪费很多空间。 @jonathen 杀手:为什么整个 bss 段用单个数字描述?? @JonathanLeffler 我的意思是所有零初始化的静态变量都进入 bss 。那么它的值不应该只是零吗?还有为什么他们没有在 .data 部分上留出空间怎么会让它变慢? @SurajJain:存储的数字是要填充零的字节数。除非没有此类未初始化的变量,否则 bss 部分的长度不会为零,即使程序加载后 bss 部分的所有字节都将为零。 可执行文件中的 .bss 部分只是一个数字。内存进程映像中的 .bss 部分通常是与 .data 部分相邻的内存,并且运行时 .data 部分通常与 .bss 组合在一起;运行时内存没有区别。有时,您可以找到 bss 的开始位置 (edata)。实际上,一旦进程映像完成,.bss 就不会存在于内存中;归零的数据是 .data 部分的简单部分。但细节因操作系统等而异。【参考方案3】:

来自 Jeff Duntemann 的 Assembly Language Step-by-Step: Programming with Linux,关于 .data 部分:

.data 部分包含初始化数据项的数据定义。初始化 data 是在程序开始运行之前具有值的数据。这些值 是可执行文件的一部分。当它们被加载到内存中时 可执行文件被加载到内存中执行。

关于 .data 部分要记住的重要一点是 您定义的初始化数据项越多,可执行文件越大 将是,并且将其从磁盘加载到内存所需的时间越长 当你运行它时。

.bss 部分:

在程序开始运行之前,并非所有数据项都需要有值。 例如,当您从磁盘文件中读取数据时,您需要有一个 数据从磁盘进入后的位置。像这样的数据缓冲区是 在程序的 .bss 部分中定义。你预留了一些 缓冲区的字节并给缓冲区命名,但你不说什么值 将出现在缓冲区中。

.data 中定义的数据项之间存在关键区别 .bss 节中定义的节和数据项: .data 部分会增加可执行文件的大小。中的数据项 .bss 部分没有。占用 16,000 字节(或更多, 有时更多)可以在 .bss 中定义并且几乎不添加任何内容 (大约50个字节的描述)到可执行文件的大小。

【讨论】:

【参考方案4】:

首先,您示例中的那些变量不是未初始化的; C 指定将未初始化的静态变量初始化为 0。

所以 .bss 的原因是具有更小的可执行文件,节省空间并允许更快地加载程序,因为加载器可以分配一堆零,而不必从磁盘复制数据。

运行程序时,程序加载器会将 .data 和 .bss 加载到内存中。写入驻留在 .data 或 .bss 中的对象因此只会进入内存,它们不会在任何时候刷新到磁盘上的二进制文件。

【讨论】:

【参考方案5】:

System V ABI 4.1 (1997)(又名 ELF 规范)也包含答案:

.bss 此部分包含未初始化的数据,这些数据有助于 程序的内存映像。根据定义,系统初始化 程序开始运行时数据为零。该节不占用文件空间,如节类型SHT_NOBITS所示。

表示段名.bss是保留的,有特殊效果,特别是不占用文件空间,比.data更有优势。

当然,缺点是当操作系统将它们放入内存时,所有字节都必须设置为0,这更具限制性,但却是一个常见的用例,并且适用于未初始化的变量。

SHT_NOBITS 部分类型文档重复了该声明:

sh_size 该成员以字节为单位给出节的大小。除非section类型是SHT_NOBITS,否则section占用sh_size 文件中的字节。 SHT_NOBITS 类型的部分可能有一个非零 大小,但不占用文件空间。

C 标准没有提及节,但我们可以使用objdumpreadelf 轻松验证变量在Linux 中的存储位置,并得出结论,未初始化的全局变量实际上存储在.bss 中。例如看这个答案:What happens to a declared, uninitialized variable in C?

【讨论】:

【参考方案6】:

***文章 .bss 提供了一个很好的历史解释,因为该术语来自 1950 年代中期(yippee mybirthday;-)。

在过去,每一点都是宝贵的,所以任何表示预留空白空间的方法都是有用的。这个 (.bss) 是卡住的那个。

.data 部分用于非空空间,而是输入(您的)定义的值。

【讨论】:

以上是关于为啥需要 .bss 段?的主要内容,如果未能解决你的问题,请参考以下文章

数据段代码段堆栈段BSS段的区别

知识点收藏

Linux进程的五个段

拷贝代码和连接脚本的改进

[转] bss段data段text段

BSS段数据段代码段堆与栈