为什么未初始化的全局变量是弱符号?

似乎未初始化的全局变量在Gcc中被视为弱符号。 这背后的原因是什么?

gcc,在C模式下:

未声明为extern未初始化的全局变量被视为“常见”符号,而不是弱符号。

公共符号在链接时合并,以便它们都引用相同的存储; 如果多个对象尝试初始化此类符号,则会出现链接时错误。 (如果它们没有在任何地方明确初始化,它们将被放置在BSS中,即初始化为0。)

gcc,在C ++模式下:

不一样 – 它没有做常见的符号。 未声明为extern “未初始化的”全局变量被隐式初始化为默认值(对于简单类型或默认构造函数为0)。


在任何一种情况下,弱符号允许初始化符号在链接时被相同名称的非弱初始化符号覆盖。


为了说明(这里集中讨论C案例),我将使用主程序的4个变体,除了声明global的方式外,它们都是相同的:

  1. main_init.c

     #include  int global = 999; int main(void) { printf("%d\n", global); return 0; } 
  2. main_uninit.c ,省略了初始化:

     #include  int global; int main(void) { printf("%d\n", global); return 0; } 
  3. main_uninit_extern.c ,它添加了extern关键字:

     #include  extern int global; int main(void) { printf("%d\n", global); return 0; } 
  4. main_weak_init.c ,初始化global并将其声明为弱符号:

     #include  int global __attribute__((weak)) = 999; int main(void) { printf("%d\n", global); return 0; } 

another_def.c初始化相同的全局:

 int global = 1234; 

main_uninit.c使用main_uninit.c给出0:

 $ gcc -o test main_uninit.c && ./test 0 

但是当包含another_def.c时,显式初始化了global ,我们得到了预期的结果:

 $ gcc -o test main_uninit.c another_def.c && ./test 1234 

(请注意,如果您使用的是C ++,则此情况会失败。)

如果我们尝试使用main_init.canother.def.c ,我们有2个global初始化,这将无效:

 $ gcc -o test main_init.c another_def.c && ./test /tmp/cc5DQeaz.o:(.data+0x0): multiple definition of `global' /tmp/ccgyz6rL.o:(.data+0x0): first defined here collect2: ld returned 1 exit status 

main_uninit_extern.c本身根本不起作用main_uninit_extern.c关键字使符号成为普通的外部引用而不是公共符号,因此链接器会抱怨:

 $ gcc -o test main_uninit_extern.c && ./test /tmp/ccqdYUIr.o: In function `main': main_uninit_extern.c:(.text+0x12): undefined reference to `global' collect2: ld returned 1 exit status 

一旦包含来自another_def.c的初始化,它就可以正常工作:

 $ gcc -o test main_uninit_extern.c another_def.c && ./test 1234 

使用main_init_weak.c本身给出了我们将弱符号初始化为(999)的值,因为没有什么可以覆盖它:

 $ gcc -o test main_init_weak.c && ./test 999 

但是从another_def.cmain_init_weak.c另一个定义在这种情况下确实有效,因为那里的强定义会覆盖main_init_weak.c的弱定义:

 $ gcc -o test main_init_weak.c another_def.c && ./test 1234 

问题是基于一个不正确的前提。 未初始化的全局变量不是弱符号。

显然,问题是指在多个翻译单元中使用外部链接定义相同的未初始化对象的能力。 forms上,它是不允许的 – 它在C和C ++中都是错误的。 但是,至少在C语言中,C99标准将其识别为该语言的“通用扩展”,并在许多现实生活中编译器中实现

J.5常用扩展

J.5.11多个外部定义

1对象的标识符可能有多个外部定义,有或没有明确使用关键字extern; 如果定义不一致,或者初始化多个,则行为未定义(6.9.2)。

请注意,与流行的看法相反,C语言明确禁止在程序中引入具有外部链接的实体的多个定义,就像C ++一样。

6.9外部定义

5外部定义是外部声明,它也是函数(内联定义除外)或对象的定义。 如果在表达式中使用通过外部链接声明的标识符(而不是作为sizeof运算符的操作数的一部分,其结果是整数常量),则整个程序中的某个地方应该只有一个标识符的外部定义; 否则,不得超过一个

但是,允许这种扩展的扩展在许多C编译器中非常流行,其中GCC恰好是一个。

这是你的意思吗?

weak.c

 #include  int weak; /* global, weak, zero */ int main(void) { printf("weak value is %d.\n", weak); return 0; } 

strong.c

 int weak = 42; /* global, strong, 42 */ 

样品运行

  $ gcc weak.c
 $ ./a.out
弱值为0。
 $ gcc weak.c strong.c
 $ ./a.out
弱值是42。 

int weak; 在weak.c中是一个声明,而不是一个定义。 或者你可能会说这是一个暂定的定义。 当该目标文件在最终程序中链接或在weak.c 链接时,真正的定义weak.c 这是一个常见的扩展,是gcc使用的(感谢Andrey)。

全局符号的任何多重定义都是未定义的行为,因此gcc(或者更确切地说是GNU binutils链接器)可以随心所欲地执行任何操作。 在实践中,它遵循传统行为以避免破坏依赖于此行为的代码。