有两个不同的概念:变量进入哪个“部分”及其“可见性”
为了比较,我添加了一个.bss 部分变量:
char global_int = 11;
char nondata_int;
int
main(int argc, char *argv[])
{
}
使用cc -S 编译产生:
.file "fix1.c"
.text
.globl global_int
.data
.type global_int, @object
.size global_int, 1
global_int:
.byte 11
.comm nondata_int,1,1
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movq %rsi, -16(%rbp)
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 8.3.1 20190223 (Red Hat 8.3.1-2)"
.section .note.GNU-stack,"",@progbits
注意.data 将global_int 变量放入数据部分。并且,.comm 将nondata_int 放入.bss 部分
另外,请注意 .globl 以使变量具有全局可见性(即可以被其他 .o 文件看到)。
简单地说,.data 和/或.bss 是变量所在的节。而且,全局 [.globl] 是可见性。如果你这样做了:
static int foobar = 63;
然后,foobar 将进入 .data 部分,但在本地。在下面的nm 输出中,而不是D,它将是d 来指示本地/静态可见性。其他.o 文件将不能够看到这个[或链接到它]。
.o 程序的nm 产生:
0000000000000000 D global_int
0000000000000000 T main
0000000000000001 C nondata_int
并且,最终可执行文件的nm -g 会产生:
000000000040401d B __bss_start
0000000000404018 D __data_start
0000000000404018 W data_start
0000000000401050 T _dl_relocate_static_pie
0000000000402008 R __dso_handle
000000000040401d D _edata
0000000000404020 B _end
0000000000401198 T _fini
000000000040401c D global_int
w __gmon_start__
0000000000401000 T _init
0000000000402000 R _IO_stdin_used
0000000000401190 T __libc_csu_fini
0000000000401120 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
0000000000401106 T main
000000000040401e B nondata_int
0000000000401020 T _start
0000000000404020 D __TMC_END__
更新:
感谢您的回答。关于 And,.comm 将 nondata_int 放入 .bss 部分。你能解释一下吗?我没有看到任何对 .bss 的引用,那么这两者有什么关系?
当然。可能有更严格的解释,但松散,当你这样做时:
int nondata_int;
您正在定义一个“通用”部分变量[历史起源来自 Fortran 的通用]。
当链接[创建最终的可执行文件]时,如果没有其他.o[或.a]已经为其声明了一个值,它将被放入.bss部分B 符号。
但是,如果 另一个 .o 定义了它(例如define_it.c):
int nondata_int = 43;
在那里,define_it.o 将把它作为D 符号放在.data 部分中
那么,当你把两者联系起来时:
gcc -o executable fix1.o define_it.o
然后,在executable 中,它将作为D 符号转到.data 部分。
所以,.o 文件具有/使用 .comm [汇编程序指令] 和 C 通用部分。
可执行文件只有.data 和.bss。因此,给定 .o 文件,如果 从未 初始化了一个通用符号,则该通用符号将转到 [被提升为] .bss,如果 any 则为 .o .data已经初始化了。
简单地说,.comm/C 是一个建议,.data 和 .bss 是一个“承诺”
这是一种很好的方式。从技术上讲,在fix1.c 中,如果我们事先知道我们将与define_it.o 链接,我们[可能] 想做:
extern char nondata_int;
然后,在fix1.o 中, 将被标记为“未定义”符号(即nm 将显示U)。
但是,如果fix1.o没有链接到任何定义该符号的东西,链接器会抱怨未定义的符号。
通用符号允许我们拥有 多个 .o 文件,每个都可以这样做:
int nondata_int;
它们都产生C 符号。链接器将所有内容组合起来生成一个 single 符号。
所以,同样常见的C 符号是:
我想要一个名为 X 的全局变量,并且我希望它与在任何其他 .o 文件中找到的 X 相同,但不要抱怨符号被多重定义。如果那些.o 文件中的一个 [和仅 个] 给它一个已初始化 值,我想从该值中受益。
历史上...
IIRC [我可能错了],common 被添加到 [链接器] 以支持 Fortran COMMON 声明/变量。
也就是说,所有 fortran .o 文件只是将一个符号声明为通用 [它的全局概念],但预期 fortran 链接器会将它们组合起来。
Classic/old fortran 只能将变量指定为 COMMON(即在 C 中,等同于 int val;)但 fortran 没有有全局初始化器(即它有 not 有extern int val; 或int val = 1;)
这个通用对 C 很有用,所以在某个时候它被添加了。
在过去的美好时光 (tm),通用链接器类型不 存在,除了一个 .o 文件和一个 [并且只有一个] 宣布它。声明它的.o 可以用值(例如)int val = 1; 或没有(例如)int val; 定义它,但所有其他.o 文件必须使用extern int val;