似乎未初始化的全局变量在Gcc中被视为弱符号。这背后的原因是什么?
答案 0 :(得分:29)
gcc,在C模式下:
未声明为extern
的未初始化全局变量被视为“常用”符号,而不是弱符号。
在链接时合并公共符号,以便它们都引用相同的存储;如果多个对象尝试初始化此类符号,则会出现链接时错误。 (如果它们没有在任何地方明确初始化,它们将被放置在BSS中,即初始化为0。)
gcc,在C ++模式下:
不一样 - 它没有做常见符号的事情。未声明为extern
的“未初始化的”全局变量被隐式初始化为默认值(对于简单类型或默认构造函数为0)。
在任何一种情况下,弱符号都允许在链接时用一个同名的非弱初始化符号覆盖初始化符号。
为了说明(这里集中讨论C案例),我将使用主程序的4个变体,除了声明global
的方式外,它们都是相同的:
<强> main_init.c 强>:
#include <stdio.h>
int global = 999;
int main(void) { printf("%d\n", global); return 0; }
main_uninit.c ,省略了初始化:
#include <stdio.h>
int global;
int main(void) { printf("%d\n", global); return 0; }
main_uninit_extern.c ,其中添加了extern
关键字:
#include <stdio.h>
extern int global;
int main(void) { printf("%d\n", global); return 0; }
main_weak_init.c ,初始化global
并将其声明为弱符号:
#include <stdio.h>
int global __attribute__((weak)) = 999;
int main(void) { printf("%d\n", global); return 0; }
和 another_def.c 初始化相同的全局:
int global = 1234;
单独使用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.c
和another.def.c
,我们会对global
进行2次初始化,这将无效:
$ 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
本身根本不起作用 - extern
关键字使符号成为普通的外部引用而不是公共符号,因此链接器会抱怨:
$ 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.c
引入另一个定义确实适用于这种情况,因为那里的强定义超越了main_init_weak.c
中的弱定义:
$ gcc -o test main_init_weak.c another_def.c && ./test
1234
答案 1 :(得分:10)
问题是基于一个不正确的前提。未初始化的全局变量不是弱符号。
显然,问题是指在多个翻译单元中使用外部链接定义相同的未初始化对象的能力。形式上,它是不允许的 - 它在C和C ++中都是错误的。但是,至少在C语言中,C99标准将其识别为该语言的“通用扩展”,并在许多现实生活中编译器中实现
J.5常见扩展程序
J.5.11多个外部定义
1 可能有多个外部 标识符的定义 对象,有或没有显式 使用关键字extern;如果 定义不同意,或不止一个 初始化,行为是 undefined(6.9.2)。
请注意,与普遍看法相反,C语言明确禁止在程序中引入具有外部链接的实体的多个定义,就像C ++一样。
6.9外部定义
5 外部定义是 外部声明也是一个 函数的定义(除了 内联定义)或对象。如果 用外部声明的标识符 链接用于表达式 (除了作为操作数的一部分 一个sizeof运算符,其结果是 整数常数),在某处 整个程序应该完全正确 一个外部定义 标识符;否则,应该有 不超过一个。
然而,允许这种扩展的扩展在许多C编译器中非常流行,其中GCC恰好是一个。
答案 2 :(得分:3)
这是你的意思吗?
weak.c
#include <stdio.h>
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 weak value is 0. $ gcc weak.c strong.c $ ./a.out weak value is 42.
weak.c中的
这是一个常见的扩展,是gcc使用的(感谢Andrey)。int weak;
是声明,而不是定义。或者您可能会说这是一个暂定的定义。 当该对象文件在最终程序或strong.c
中链接时,真正的定义在weak.c
。
答案 3 :(得分:3)
全局符号的任何多重定义都是未定义的行为,因此gcc(或者更确切地说是GNU binutils链接器)可以随心所欲地执行任何操作。在实践中,它遵循传统行为,以避免破坏依赖于此行为的代码。