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

问题描述 投票:4回答:1
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;

file2:

#include<stdio.h>
int a; 

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

作为使用clang-7编译的C文件,链接器不会产生错误,并且我假设它会将未初始化的全局符号'a'转换为extern符号(将其视为已编译为extern声明进行处理)。当使用clang ++-7编译C ++文件时,链接器会产生多定义错误。


[更新:链接的问题确实回答了我的问题的第一个示例,特别是'在C中,如果在同一个翻译单元中早晚发现了实际的外部定义,则该暂定定义仅充当声明。”和“ C ++没有“临时定义””。

对于第二种情况,如果我打印fa,那么它确实打印2,因此显然链接程序已正确链接了它(但是我以前曾假设,临时定义将由编译器初始化为0作为全局定义。并会导致链接错误)。

事实证明,两个文件中的int i[];临时定义也都链接到一个定义。 int i[5];也是.common中的暂定定义,只是表示给汇编器的大小不同。前者被称为具有不完整类型的暂定定义,而后者是具有完整类型的暂定定义。

使用C编译器会发生的事情是,在符号表中将[[int a设置为.common中的强绑定弱全局,并且未对其进行初始化(其中.common表示弱全局)。一个外部符号),并且链接程序将做出必要的extern int a,即,如果翻译单元中存在具有相同标识符的强绑定全局,则链接器将忽略使用decision定义的所有弱绑定全局。这将是一个多重定义错误(但是,如果未找到强边界和1个弱边界,则输出为单个弱边界,并且如果未找到强边界而只有两个弱边界,则将在命令行上的第一个文件并输出单个弱绑定。尽管两个弱绑定是链接器的两个definitions(因为它们已由编译器初始化为0),但这不是多定义错误,因为它们都是弱约束的),然后解析所有.common符号以指向强/弱约束的强全局变量。#pragma weakhttps://godbolt.org/z/Xu_8tY由于https://docs.oracle.com/cd/E19120-01/open.solaris/819-0690/chapter2-93321/index.html是用#pragma弱声明的,因此它是弱绑定的,并由compiler归零并放入.bss(即使它是弱全局变量,也不会进入.common,因为它是弱绑定的;如果未初始化,所有弱绑定变量都放在.bss中,并由编译器初始化;如果已初始化,则所有变量都位于.data中。如果未使用baz声明,则#pragma weak将通用,并且如果未找到弱/强绑定的强全局符号,则链接器会将其归零。

C ++编译器在baz]成为强绑定强全局变量,并将其初始化为0:int a,因此链接器将其视为多重定义。
c++ c clang language-lawyer clang++
1个回答
2
投票
我将解决问题的C端,因为我对这种语言更加熟悉,而且您似乎已经很清楚C ++端为什么能如此工作。欢迎其他人添加详细的C ++答案。

正如您所注意到的,在第一个示例中,C将行https://godbolt.org/z/aGT2-o视为

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

在第二个示例中,int a;也具有file2的临时定义。此翻译单元中没有外部定义,因此

行为与翻译完全一样单元包含该标识符的文件范围声明,其复合类型从转换单元,其初始值设定为0。[6.9.2(1)]

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

如果在表达式中使用通过外部链接声明的标识符,(不是作为sizeof或_Alignof运算符的操作数的一部分,其结果是整数常数),在整个程序的某处,对于标识符否则,不得超过一个。

因此,在C标准下,程序的行为是不确定的,编译器可以根据自己的喜好自由执行。 (但请注意,不需要进行诊断。)对于您的特定实现,编译器选择执行的操作不是描述鼻恶魔,而是您描述的内容:使用目标文件格式的file2功能,并使链接器合并定义合二为一。尽管标准没有要求,但是这种行为至少在Unix上是传统的,并且在J.5.11中被标准称为“通用扩展名”(无双关)。

我认为此功能非常方便,但是由于只有在您的目标文件格式支持它的情况下才有可能,所以我们真的不能期望C标准作者对此进行强制规定。

据我所知,[common并没有非常清楚地记录此行为,但是具有相同行为的clanggcc选项下为describes it。在任何一个编译器上,都可以使用-fcommon禁用它,然后您的程序应该因多定义错误而无法链接。

© www.soinside.com 2019 - 2024. All rights reserved.