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

Different compilation results not using extern in C vs in C++

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

main.c

#include <stdio.h>
#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 <stdio.h>
#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)

根据this link

A tentative definition is a declaration that may or may not act as a definition. If an actual external definition is found earlier or later in the same translation unit, then the tentative definition just acts as a declaration.

所以我的变量 def_var 应该在每个翻译单元的末尾定义,然后导致多个定义(就像 C++ 所做的那样)。为什么用 gcc/clang 编译时不是这样?

严格来说,这也不是有效的 C。在

中说了这么多

6.9 External definitions - p5

An external definition is an external declaration that is also a definition of a function (other than an inline definition) or an object. 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++ 在这方面没有什么不同。

[basic.def.odr]/4

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program outside of a discarded statement; no diagnostic required. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see [class.ctor], [class.dtor] and [class.copy]). An inline function or variable shall be defined in every translation unit in which it is odr-used outside of a discarded statement.

同样,"shall" 要求,它明确表示不需要诊断。正如您可能已经注意到的,本段可以应用的机制要多得多。因此,GCC 和 Clang 的前端可能需要更加努力地工作,因此能够诊断它,尽管不需要这样做。

无论哪种方式,程序都是 ill-formed。


M.M pointed out in a 一样,C 标准有一个信息部分,在 zwol 的回答中提到了这个扩展。

J.5.11 Multiple external definitions

There may be more than one external definition for the identifier of an object, with or without the explicit use of the keyword extern; if the definitions disagree, or more than one is initialized, the behavior is undefined (6.9.2).

我相信您正在观察称为“common symbols”的 C 扩展,由大多数但不是全部 Unix-lineage C 编译器实现,最初 (IIUC) 是为了与 FORTRAN 兼容。该扩展概括了 StoryTeller 对多个翻译单元的回答中描述的 "tentative definitions" 规则。所有具有相同名称且没有初始值设定项的外部对象定义,

int foo; // at file scope

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

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

然后所有没有初始值设定项的外部定义都被视为外部声明。 C++ 编译器不实现此扩展,因为(过于简单化)没有人想弄清楚它在模板存在的情况下应该做什么。对于 GCC 和 Clang,您可以使用 -fno-common 禁用扩展,但其他 Unix C 编译器可能无法关闭它。