Lew*_*sey 5 c c++ clang language-lawyer clang++
int a;
int a=3; //error as cpp compiled with clang++-7 compiler but not as C compiled with clang-7;
int main() {
}
Run Code Online (Sandbox Code Playgroud)
对于 C,编译器似乎将这些符号合并为一个全局符号,但对于 C++,这是一个错误。
文件 1:
int a = 2;
Run Code Online (Sandbox Code Playgroud)
文件2:
#include<stdio.h>
int a;
int main() {
printf("%d", a); //2
}
Run Code Online (Sandbox Code Playgroud)
作为用 clang-7 编译的 C 文件,链接器不会产生错误,我假设它将未初始化的全局符号“a”转换为外部符号(将其视为被编译为外部声明)。作为使用 clang++-7 编译的 C++ 文件,链接器会产生多重定义错误。
更新:链接的问题确实回答了我的问题中的第一个示例,特别是“在 C 中,如果在同一翻译单元中较早或较晚找到实际的外部定义,则暂定定义仅作为声明。” 和'C++ 没有“暂定定义”'。
至于第二种情况,如果我 printf a,那么它会打印 2,所以显然链接器已经正确链接了它(但我以前假设编译器将临时定义初始化为 0 作为全局定义,并且会导致链接错误)。
事实证明,int i[];两个文件中的暂定定义也与一个定义相关联。int i[5];也是 .common 中的一个暂定定义,只是向汇编器表达了不同的大小。前者称为类型不完整的暂定定义,而后者是类型完整的暂定定义。
C 编译器发生的情况是,int a在 .common 中将其设为强绑定弱全局,并在符号表中未初始化(其中 .common 表示弱全局)(而extern int a将是外部符号),并且链接器做出必要的决定,即它忽略所有使用#pragma weakif定义的弱边界全局变量,如果在翻译单元中存在具有相同标识符的强边界全局变量,其中 2 个强边界将是多重定义错误(但如果它没有找到强边界,而 1 weak-bound,输出是单个弱边界,如果没有找到强边界,只有两个弱边界,则选择命令行第一个文件中的定义,输出单个弱边界。虽然两个弱边界-bounds 是两个定义到链接器(因为它们被编译器初始化为 0),这不是多重定义错误,因为它们都是弱绑定)然后解析所有 .common 符号以指向强/弱绑定强全局。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 如果没有找到弱/强绑定强全局符号,则链接器会将其归零。
C++ 编译器int a在 .bss 中创建一个强绑定强全局并将其初始化为 0:https://godbolt.org/z/aGT2-o,因此链接器将其视为多重定义。
更新 2:
GCC 10.1 默认为-fno-common. 因此,全局变量目标在各种目标上更有效。在 C 中,具有多个暂定定义的全局变量现在会导致链接器错误(如 C++)。有了-fcommon这样的定义连接过程中默默合并。
我将解决问题的 C 端,因为我更熟悉该语言,并且您似乎已经很清楚为什么 C++ 端会这样工作。欢迎其他人添加详细的 C++ 答案。
正如您所指出的,在第一个示例中,C 将该行int a;视为暂定定义(请参阅N2176中的 6.9.2 )。后者int a = 3;是带有初始值设定项的声明,因此它是外部定义。因此,早期的暂定定义int a;仅被视为声明。因此,追溯起来,您首先在文件范围内声明了一个变量,然后定义了它(使用初始值设定项)。没问题。
在您的第二个示例中,file2也有 的暂定定义a。该翻译单元中没有外部定义,因此
其行为与翻译单元包含该标识符的文件范围声明完全相同,复合类型截至翻译单元末尾,初始值设定项等于 0。 [6.9.2 (1)]
也就是说,就好像您int a = 0;用file2. 现在您的程序中有两个外部定义a,一个是 in file1,另一个是 in file2。这违反了 6.9 (5):
如果在表达式中使用通过外部链接声明的标识符(而不是作为结果为整型常量的 sizeof 或 _Alignof 运算符的操作数的一部分),则在整个程序中的某处应该有一个该标识符的外部定义;否则,不得超过一个。
因此,在 C 标准下,程序的行为是未定义的,编译器可以自由地执行它喜欢的操作。(但请注意,不需要诊断。)对于您的特定实现,您的编译器不会选择做您所描述的事情,而不是召唤鼻恶魔:使用common目标文件格式的功能,并让链接器将定义合并为一个。尽管标准没有要求,但这种行为至少在 Unix 上是传统的,并且在 J.5.11 中被标准称为“通用扩展”(无双关语)。
在我看来,此功能非常方便,但由于只有您的目标文件格式支持它才可能实现,因此我们不能真正指望 C 标准作者强制使用它。
clang据我所知,并没有非常清楚地记录此行为,但是gcc具有相同行为的 ,在该选项下描述了它-fcommon。在任一编译器上,您可以使用 禁用它-fno-common,然后您的程序应该无法链接并出现多重定义错误。
| 归档时间: |
|
| 查看次数: |
273 次 |
| 最近记录: |