不同的编译结果不使用C ++中的C vs extern

当我在两个不同的源文件中声明一个全局变量并且只在其中一个源文件中定义它时,我得到的C ++编译结果与C相比不同。请参阅以下示例:

main.c中

#include  #include "func.h" // only contains declaration of void print(); int def_var = 10; int main() { printf("%d\n", def_var); return 0; } 

func.c

 #include  #include "func.h" /* extern */int def_var; // extern needed for C++ but not for C? void print() { printf("%d\n", def_var); } 

我用以下命令编译:

 gcc/g++ -c main.c -o main.o gcc/g++ -c func.c -o func.o gcc/g++ main.o func.o -o main 

g ++ / clang ++抱怨multiple definition of def_var (这是我预期的行为,当不使用extern时)。 gcc / clang编译得很好。 (使用gcc 7.3.1和clang 5.0)

根据这个链接 :

暂定定义是一种可能或可能不作为定义的声明。 如果在同一翻译单元中较早或较晚发现实际外部定义,则暂定定义仅作为声明。

所以我的变量def_var应该在每个翻译单元的末尾定义,然后产生多个定义(就像C ++一样)。 为什么在使用gcc / clang编译时并非如此?

严格来说,这也不是有效的C. 说得多

6.9外部定义 – 第5页

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

对于具有外部链接的标识符,您有两个定义。 您违反了该要求,行为未定义。 连接和工作的计划并不与此相反。 它不需要被诊断。

值得注意的是,C ++在这方面没有什么不同。

[basic.def.odr / 4

每个程序应该只包含每个非内联函数或变量的一个定义,该函数或变量在废弃语句之外的程序中使用。 无需诊断。 该定义可以在程序中明确显示,可以在标准或用户定义的库中找到,或者(在适当的时候)隐式定义(参见[class.ctor],[class.dtor]和[class.copy] ])。 内联函数或变量应在每个翻译单元中定义,在该单元中,在废弃的语句之外使用它。

同样,“必须”的要求,并明确表示不需要诊断。 您可能已经注意到,本段可以应用的机器相当多。 所以GCC和Clang的前端可能需要更加努力,因此尽管没有被要求,但是能够对其进行诊断。

无论如何,该计划都是不合理的。


正如MM在评论中指出的那样,C标准有一个信息部分提到了zwol答案中的扩展。

J.5.11多个外部定义

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

我相信你正在观察被称为“ 常见符号 ”的扩展,由大多数(但不是全部)Unix-lineage C编译器实现,最初(IIUC)与FORTRAN兼容。 该扩展概括了StoryTeller对多个翻译单元的回答中描述的“暂定定义”规则。 所有外部对象定义具有相同的名称,没有初始化程序,

 int foo; // at file scope 

被折叠成一个,即使它们出现在多个TU中,并且如果存在具有该名称的初始值设定项的外部定义,

 int foo = 1; // different TU, also file scope 

那么所有没有初始化器的外部定义都被视为外部声明 。 C ++编译器没有实现这个扩展,因为(过度简化)没有人想知道在存在模板时它应该做什么。 对于GCC和Clang,您可以使用-fno-common禁用扩展,但其他Unix C编译器可能无法将其关闭。