C语言中的bss段

C语言中的bss段,c,memory-management,data-segment,C,Memory Management,Data Segment,在对问题“”的其中一个答案中,我看到有关bss的解释如下: Bss是特殊的:。Bss对象不占用对象文件中的任何空间,通过将所有未专门初始化的符号分组在一起,可以轻松地一次将它们归零 但当我在对象文件上使用大小时,由代码生成: #include <stdio.h> int uninit_global_var; int init_global_var=5; int main() { int local_var; return 0; } 并看到bss基于具有全局范围的未初始

在对问题“”的其中一个答案中,我看到有关bss的解释如下:

Bss是特殊的:。Bss对象不占用对象文件中的任何空间,通过将所有未专门初始化的符号分组在一起,可以轻松地一次将它们归零

但当我在对象文件上使用大小时,由代码生成:

#include <stdio.h>
int uninit_global_var;
int init_global_var=5;

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

并看到bss基于具有全局范围的未初始化数据成员而不断增长。那么,有人能证明上述说法的合理性吗?

我不知道确切的答案,但我有根据的猜测是:

bss段的大小在对象文件中,并通过大小->必须分配显示


但是当bss段增长时,对象文件不会增长。

a.out
可能不是一个对象文件,它可能是一个ELF-full可执行文件。可重定位对象(通常命名为name.o)是链接发生之前的中间文件。请参阅gcc的-c选项。

如果删除stdio.h,您的输出可能会更有意义。让我们忽略该库,因为它包含内部变量

在您的特定情况下,会发生以下情况:

int uninit_global_var;
由于这是在中的文件范围内分配的变量,因此它具有静态存储持续时间,就像任何声明为
static
的变量一样。C标准要求,如果程序员没有明确初始化具有静态存储持续时间的变量(如本例),则必须在程序启动之前将其设置为零。所有这些变量都放在
.bss
段中

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

   int local_var;
此变量具有自动存储持续时间(本地)。编译器很可能会优化掉这个变量,因为它没有任何用途。但让我们假设这样的优化不会发生。然后,当变量所在的作用域(块)被执行时,该变量将在运行时被分配,一旦找到该作用域(它超出作用域),该变量就不再存在。它将在堆栈或CPU寄存器中分配。换句话说,在链接时,该变量仅以程序代码的形式存在,其形式是一些汇编指令,如“在堆栈上推一个int”,然后“从堆栈中弹出一个int”

如何初始化这些不同类型的变量取决于系统。但在调用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”变量相同。“本地”变量可以分配到任何地方,因此您可以从这两个变量中获得任何类型的奇怪地址。

bss段会增长,但您的二进制文件中不需要此段(请参阅objcopy)


因此,如果您最终将此代码放入某种ROM中,它将不占用任何空间,但需要RAM中的空间(以及将其初始化为0的代码)。

Cdarke。你是对的。根据您的输入,我尝试了以下实验
inta[10000]={5};int b[10000]={10}
在本例中,.o文件的大小是80698,但是
inta[10000];int b[10000]
;.o文件的大小只有698。因此,基本上,对象文件的大小不依赖于bss数据的增长。你能不能再多说几点。我看到其他人(@Lundin)对这一点的阐述比我好得多。完全正确!请注意,BSS引用的内存当然是在程序首次启动时由操作系统在进程中分配为数据页(并初始化为零)。所以它只是目标文件或程序可执行文件中的一个数字,但它在运行时会占用进程中的实际空间。如果在未初始化的情况下声明一个大数组,size命令将显示所需的空间,但elf文件的实际大小不会改变。
%p
需要类型为
void*
的参数。将那些
char*
s转换为
void*
@CoolGuy是的,每个指向数据的指针都可以安全地转换为
void*
。事实上,在
void*
和另一个指针类型之间的强制转换不需要显式强制转换。那么为什么答案告诉我们要强制转换呢?@CoolGuy printf显然是一些特例。我非常怀疑,如果你不使用void,你在任何平台上都会遇到任何实际问题*。
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);
}