为什么 c 和 c++ 以不同方式对待未初始化变量的重新定义?

Why do c and c++ treat redefinitions of uninitialised variables differently?

int a;
int a=3; //error as cpp compiled with clang++-7 compiler but not as C compiled with clang-7;

int main() {

}

对于 C,编译器似乎将这些符号合并为一个全局符号,但对于 C++,这是一个错误。

Demo


文件 1:

int a = 2;

文件 2:

#include<stdio.h>
int a; 

int main() {
    printf("%d", a); //2
}

作为使用 clang-7 编译的 C 文件,linker 不会产生错误,我假设它将未初始化的全局符号 'a' 转换为外部符号(将其视为被编译为外部声明)。作为使用 clang++-7 编译的 C++ 文件,linker 会产生多重定义错误。


更新:linked 问题确实回答了我问题中的第一个示例,特别是 'In C, If an actual external definition is found earlier or later in the same translation unit, then the tentative definition just acts as a declaration.' 和 'C++ does not have “tentative definitions”'。

至于第二种情况,如果我 printf a,那么它会打印 2,所以显然 linker 已经 link 正确地编辑了它(但我以前会假设一个暂定的定义将被编译器初始化为 0 作为全局定义,并会导致 link 错误)。

事实证明,两个文件中的 int i[]; 暂定定义也被 link 编辑为一个定义。 int i[5];也是.common中的一个暂定定义,只是对汇编器表达的大小不同而已。前者称为不完整类型的暂定定义,后者称为完整类型的暂定定义。

C 编译器发生的情况是 int a 在 .common 中被设为弱全局强绑定并且未初始化(其中 .common 表示弱全局)在符号 table 中(而 extern int a 将是一个外部符号),并且 linker 使必要的 decision, i.e. it ignores all weak-bound globals defined using #pragma weak if there is a strong-bound global with the same identifier in a translation unit, where 2 strong-bounds would be a multiple definition error (but if it finds no strong-bounds and 1 weak-bound, the output is a single weak-bound, and if it finds no strong-bounds but two weak-bounds, it chooses the definition in the first file on the command line and outputs the single weak-bound. Though two weak-bounds are two definitions to the linker (because they are initialised to 0 by the compiler), it is not a multiple definition error, because they are both weak-bound) and then resolves all .common symbols to point to the strong/weak-bound strong global. https://godbolt.org/z/Xu_8tY https://docs.oracle.com/cd/E19120-01/open.solaris/819-0690/chapter2-93321/index.html 由于 baz 是用 #pragma weak 声明的,它是弱绑定的,由 编译器 清零并放入 .bss(即使它是一个弱全局变量,它也不会不要放在 .common 中,因为它是弱绑定的;所有弱绑定变量如果未初始化并由编译器初始化,则放在 .bss 中,如果已初始化,则放在 .data 中)。如果它没有用 #pragma weak 声明,baz 将成为共同点,如果没有找到 weak/strong-bound 强全局符号,linker 会将其归零。

C++ 编译器使 int a 成为 .bss 中的强绑定强全局变量并将其初始化为 0: https://godbolt.org/z/aGT2-o,因此 linker 将其视为多重定义。


更新 2:

GCC 10.1 默认为 -fno-common。因此,全局变量目标在各种目标上更有效。在 C 中,具有多个暂定定义的全局变量现在会导致 linker 错误(如 C++)。使用 -fcommon 时,此类定义会在 linking 期间静默合并。

我将解决问题的 C 端,因为我更熟悉该语言,而且您似乎已经很清楚 C++ 端为何如此工作。欢迎其他人补充详细的C++答案。

如您所述,在您的第一个示例中,C 将行 int a; 视为 暂定定义 (请参阅 N2176 中的 6.9.2)。后面的int a = 3;是一个带有初始化器的声明,所以是一个外部定义。因此,较早的暂定定义 int a; 仅被视为声明。因此,追溯地,您首先在文件范围内声明了一个变量,然后又定义了它(使用初始化程序)。没问题。

在你的第二个例子中,file2 也有一个 a 的暂定定义。这个翻译单元没有外部定义,所以

the behavior is exactly as if the translation unit contains a file scope declaration of that identifier, with the composite type as of the end of the translation unit, with an initializer equal to 0. [6.9.2 (1)]

也就是说,就好像你在file2中写了int a = 0;一样。现在你的程序中有两个 a 的外部定义,一个在 file1 中,另一个在 file2 中。这违反了 6.9 (5):

If an identifier declared with external linkage is used in an expression (other than as part of the operand of a sizeof or _Alignof operator whose result is an integer constant), somewhere in the entire program there shall be exactly one external definition for the identifier; otherwise, there shall be no more than one.

所以在 C 标准下,你的程序的行为是未定义的,编译器可以随心所欲。 (但请注意,不需要诊断。)对于您的特定实现,您的编译器选择做的不是召唤鼻恶魔,而是您所描述的:使用目标文件格式的 common 功能,并具有 linker 将定义合并为一个。虽然标准没有要求,但这种行为至少在 Unix 上是传统的,并且在 J.5.11 中被标准称为 "common extension"(没有双关语意)。

在我看来,此功能非常方便,但由于只有您的目标文件格式支持它才有可能,我们真的不能指望 C 标准作者强制执行它。

据我所知,

clang 没有非常清楚地记录此行为,但是 gcc 具有相同的行为,describes it 在 [=23= 下] 选项。在任何一个编译器上,您都可以使用 -fno-common 禁用它,然后您的程序应该无法 link 并出现多重定义错误。