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

Tho*_*son 8 c c++ gcc

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

Mat*_*ery 29

gcc,在C模式下:

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

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

gcc,在C++模式下:

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


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


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

  1. main_init.c:

    #include <stdio.h>
    
    int global = 999;
    
    int main(void) { printf("%d\n", global); return 0; }
    
    Run Code Online (Sandbox Code Playgroud)
  2. main_uninit.c,省略了初始化:

    #include <stdio.h>
    
    int global;
    
    int main(void) { printf("%d\n", global); return 0; }
    
    Run Code Online (Sandbox Code Playgroud)
  3. main_uninit_extern.c,添加extern关键字:

    #include <stdio.h>
    
    extern int global;
    
    int main(void) { printf("%d\n", global); return 0; }
    
    Run Code Online (Sandbox Code Playgroud)
  4. main_weak_init.c,初始化global并声明它是一个弱符号:

    #include <stdio.h>
    
    int global __attribute__((weak)) = 999;
    
    int main(void) { printf("%d\n", global); return 0; }
    
    Run Code Online (Sandbox Code Playgroud)

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

int global = 1234;
Run Code Online (Sandbox Code Playgroud)

单独使用main_uninit.c给出0:

$ gcc -o test main_uninit.c && ./test
0
Run Code Online (Sandbox Code Playgroud)

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

$ gcc -o test main_uninit.c another_def.c && ./test
1234
Run Code Online (Sandbox Code Playgroud)

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

如果我们尝试使用两者main_init.canother.def.c不是,我们有两个初始化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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

$ gcc -o test main_uninit_extern.c another_def.c && ./test
1234
Run Code Online (Sandbox Code Playgroud)

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

$ gcc -o test main_init_weak.c && ./test
999
Run Code Online (Sandbox Code Playgroud)

但是another_def.c在这种情况下引入其他定义确实有效,因为那里的强定义超越了弱定义main_init_weak.c:

$ gcc -o test main_init_weak.c another_def.c && ./test
1234
Run Code Online (Sandbox Code Playgroud)

  • 可以将该解释视为特定编译器行为的描述.它是**不正确**作为C语言行为的描述.C语言*禁止*具有外部链接的实体的多个定义(就像C++一样).观察到的行为只不过是编译器扩展. (5认同)

AnT*_*AnT 10

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

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

J.5常用扩展

J.5.11多个外部定义

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

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

6.9外部定义

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

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