C语言中的局部变量和静态变量

18

编译以下代码时:

// external definitions
int value1 = 0;
static int value2 = 0;

gcc编译器生成以下汇编代码:

.globl value1
        .bss
        .align 4
        .type   value1, @object
        .size   value1, 4
value1:
        .zero   4
        .local  value2
        .comm   value2,4,4

然而,当我将变量初始化为零以外的值时,例如:

// external definitions
int value1 = 1;
static int value2 = 1;

gcc编译器生成了以下代码:

.globl value1
        .data
        .align 4
        .type   value1, @object
        .size   value1, 4
value1:
        .long   1
        .align 4
        .type   value2, @object
        .size   value2, 4
value2:
        .long   1

我的问题是:

  1. 为什么在第一个案例中,值被分配到bss段,而在第二个案例中,值被分配到data段。
  2. 为什么在第一个案例中,value2变量被定义为.local和.comm,而在第二个案例中没有。

1
从你的C代码片段中很难判断,但是value1似乎是一个全局变量(而不是局部变量)。局部变量将分配在堆栈上。 - Codo
很难说。我认为将其放入.bss中是一个错误。虽然从标准的角度来看没有区别(未初始化的变量应该被初始化为0),但已经习惯了将初始化为0与未初始化区分开来。 - glglgl
@glglgl:.bss中的数据已经被初始化。它被初始化为零。现代系统没有提供一种创建未初始化内存的方法。 - Dietrich Epp
1
@lefty:那么你的意思是它们被定义在文件作用域中。 (与块作用域,函数作用域等相对)“内部/外部”是具有特定定义的技术术语,表示其他含义。 - Dietrich Epp
2
@glglgl:但是说把零初始化的数据放在“.bss”中是一个bug是不正确的。当然,在某些系统上,你可以强制编译器以非标准兼容的方式运行,但这并不是真正相关的。 - Dietrich Epp
显示剩余6条评论
3个回答

11
一般来说,bss 段包含未初始化的值,data 段包含已初始化的值。然而,gcc 将初始化为零的值放入 bss 段而不是 data 段,因为 bss 段在运行时会被清零,将零存储在 data 段中没有多大意义,这可以节省磁盘空间。从 man gcc 中得知:

-fno-zero-initialized-in-bss 如果目标支持 BSS 段,则 GCC 默认将初始化为零的变量放入 BSS 段中。这可以节省最终代码中的空间。此选项关闭此行为,因为某些程序明确依赖于变量进入数据段。

我不确定为什么在局部于一个对象文件的静态存储中使用.comm,它通常用于声明公共符号,如果未定义/初始化,则应该由链接器与其他对象文件中具有相同名称的符号合并,这就是为什么在第二个示例中没有使用它的原因,因为变量已经被初始化,来自as manual

.comm 声明了一个名为 symbol 的公共符号。在链接时,一个对象文件中的公共符号可以与另一个对象文件中具有相同名称的已定义或公共符号合并。


我的问题第二部分有什么想法吗? - Lefteris Laskaridis
4
这就是为什么在Stack Overflow上同时提出两个问题不是一个好主意,因为你不能选择两个答案。 - Dietrich Epp
说BSS包含未初始化的数据有点误导,因为它实际上是在运行时初始化的。主要区别在于是在编译时还是在运行时以程序二进制形式完成初始化。 - fkl
@fayyazkl 我说过它是在运行时初始化的。 - iabdalkader
@mux 我同意。只是强调初始化的重要性。我实际上看到了你最初的回答,但感觉没有提到运行时初始化部分。所以我开始写一个。后来看到你编辑过的回答。 - fkl
显示剩余3条评论

5
第一种情况是因为您用零初始化了这些值。根据 C标准(第6.7.8节),如果没有指定值,则全局整数将被初始化为0。因此,文件格式通过专门的部分进行规定,以便将二进制文件保持较小:bss。如果您查看一些 ELF规范(在I-15页上),您会发现以下内容:

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

在第一种情况下,编译器进行了优化。它不需要在实际二进制文件中占用空间来存储初始化器,因为它可以使用bss段并免费获取您想要的一个。

现在,您从外部源获取静态资源的事实有点有趣(通常不这样做)。但是在编译的模块中,它不应与其他模块共享,并且应该标记为.local。我怀疑它以这种方式执行是因为没有实际值可存储于初始化程序中。

在第二个示例中,因为您提供了非零的初始值,所以它现在驻留在已初始化的数据段data中。value1看起来非常相似,但对于value2,编译器需要保留空间以用于初始化程序。在这种情况下,它不需要标记为.local,因为它可以放置值并完成操作。它不是全局的,因为没有.globl语句。

顺便说一句,http://refspecs.linuxbase.org/是一个很好的地方,可以了解有关二进制格式等低级细节。


3

BSS 是包含在运行时初始化数据的部分,而数据段包含在程序二进制文件中初始化的数据。

现在静态变量始终会被初始化,无论是否在程序中显式地初始化。但是它们有两个不同的类别,即已初始化(DS)和未初始化(BSS)静态变量。

BSS 中存在的所有值都是未在程序代码中初始化的值,在程序加载运行时初始化为 0(如果是整数),空指针等。

因此,当您使用 0 进行初始化时,该值将进入 BSS 中,而分配其他任何值将在数据段中分配变量。

有趣的结果是,在 BSS 中初始化的数据大小不会包括在程序二进制文件中,而在数据段中初始化的数据大小会包括在内。

尝试分配一个大的静态数组并在程序中使用它。看看当它在代码中未显式地初始化时可执行文件的大小,然后用非零值进行初始化,如下所示:

static int arr[1000] = {2};

在后一种情况下,可执行文件的大小会显著增大。

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