C中的bss段

10
在对问题“关于Unix中的bss段和数据段”的一个回答中,我看到了关于bss的解释如下:
引用: .bss是特殊的:.bss对象在目标文件中不占用任何空间,并且通过将所有未经明确初始化的符号分组在一起,它们可以一次性地清零。
但是当我在生成的代码的目标文件上使用size命令时:
#include <stdio.h>
int uninit_global_var;
int init_global_var=5;

int main()
{
   int local_var;
   return 0;
}

我有以下的东西。
text    data      bss    dec     hex filename
1231     280      12    1523     5f3 a.out

看到根据全局范围的未初始化数据成员增长。那么有人能证明这个说法吗?
4个回答

10
如果您删除了stdio.h,您的输出可能会更有意义。忽略该库,因为它包含内部变量。
在您的特定情况下,发生以下情况:
int uninit_global_var;

由于这个变量是在文件范围内分配的,在静态存储期中,就像任何声明为static的变量一样。 C标准要求如果具有静态存储期的变量没有被程序员显式初始化,例如在这种情况下,它必须在程序启动之前设置为零。所有这些变量都放在.bss段中。

int init_global_var=5;

该变量也是在文件范围分配的,因此它也具有静态存储期。但在这种情况下,它由程序员初始化。C标准要求这些变量在程序启动之前被设置为给定的值。这些变量被放置在 .data 段中。

   int local_var;

这个变量具有自动存储期(局部变量)。编译器很可能会优化掉这个变量,因为它没有任何用处。但是假设不进行这样的优化,变量将在运行时分配,在其所属的作用域(块)被执行时分配,并且一旦该作用域结束(超出范围),它就会停止存在。它将被分配到堆栈或CPU寄存器中。换句话说,链接时此变量仅作为程序代码存在,以某些汇编指令的形式表示“在堆栈上推入一个整数”,然后稍后“从堆栈中弹出一个整数”。
这些不同类型的变量如何初始化取决于系统。但通常会有一些代码由编译器注入main函数调用之前。尽管这是一个过度简化的说法,但出于教学目的,您可以将程序想象成这样。
bss
{
  int uninit_global_var;
}

data
{
  int init_global_var;
}

rodata
{
  5;
}


int start_of_program (void) // called by OS
{
  memset(bss, 0, bss_size);
  memcpy(data, rodata, data_size);

  return main(); 
}

数据:4 bss:4

拥有真正非易失性存储器的嵌入式系统将与上述代码完全相同,而基于RAM的系统可能会以不同的方式解决数据初始化部分。 bss 在所有系统上都是相同的。


您可以通过运行以下程序轻松验证它们存储在不同的段中:

char uninit1;
char uninit2;
char init1 = 1;
char init2 = 2;

int main (void)
{
  char local1 = 1;
  char local2 = 2;

  printf("bss\t%p\t%p\n", &uninit1, &uninit2);
  printf("data\t%p\t%p\n", &init1, &init2);
  printf("auto\t%p\t%p\n", &local1, &local2);
}

您会发现,“uninit”变量被分配在相邻的地址上,但与其他变量分配在不同的地址上。同理,“init”变量也是如此。 “local”变量可以分配在任何地方,因此从这两个变量中得到任何奇怪的地址结果都有可能。


"%p"需要一个void*类型的参数。将这些char*强制转换为void* - Spikatrix
@CoolGuy 是的,每个指向数据的指针都可以安全地转换为void*。实际上,在void*和另一个指针类型之间的转换不需要显式的强制转换。 - Lundin
那么为什么这篇帖子的答案告诉我们要进行强制转换呢? - Spikatrix
@CoolGuy 显然printf是一些特殊情况。如果你不将其转换为void*,我非常怀疑你在任何平台上都不会遇到任何实际问题。 - Lundin

7
我不确定答案,但我有一个猜测:
bss段的大小在目标文件中,并且通过size命令可以显示它,毕竟必须分配。但是,当bss段增长时,目标文件不会增大。

请注意,BSS 引用的内存当然是在程序首次启动时由操作系统分配为数据页(并初始化为零)。因此,在目标文件或程序可执行文件中只有一个数字,但在运行时它占用了实际空间。 - Greg A. Woods
没错。如果你声明了一个巨大的数组但没有初始化它,size命令会显示所需的空间,但elf文件的实际大小不会改变。 - Wei Qiu

3
段增长了,但你的二进制文件中不需要这个段(参见objcopy)。 <如果你将这段代码放入某种ROM中,它在那里将不占用空间,但将需要RAM中的空间(以及初始化为0的代码)。/p>

2

a.out 可能不是一个目标文件,而是一个 ELF——完整的可执行文件。通常以 name.o 命名的可重定位目标文件则是链接之前的中间文件。请参见 gcc 的 -c 选项。


Cdarke。你是对的。根据你的输入,我尝试了以下实验 int a[10000]={5}; int b[10000]={10}; 在这种情况下,.o文件的大小为80698,而int a[10000]; int b[10000]; .o文件的大小仅为698。因此,基本上目标文件大小不会随着bss数据的增长而增加。你能否再详细解释一些。 - Vivek Maran
我看到其他人(@Lundin)已经比我更好地阐述了这一点。 - cdarke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接