在 header 文件中使用 extern 的优势

Advantage of using extern in a header file

这里有一个类似的问题标题,但在阅读答案时它似乎并没有解决那个特定的问题:C: What is the use of 'extern' in header files?(它更像是一个“为什么使用 header 文件?” ).

extern的以下用法中:

extern int a;
int b;

// structs have no external linkage 
typedef struct Item_ {int id;} Item;
extern Item Item_Extern;

void main(void) {

}

// later in the program or another file
int a=4;
int b=5;
Item Item_Extern = {1};

如果使用 extern,编译器是否会生成任何不同的程序集,或者这是否更像是一个类型的 metadata-tag,以便代码(或编译器)的用户明确知道定义将出现在不同的文件中(通常)或稍后出现在当前文件中,如果不是这样,它将是一个硬错误。或者,extern 'do' 还有什么吗?

简介

这里有三种关注声明形式:1

extern int x; // Declares x but does not define it.
int x;        // Tentative definition of x.
int x = 0;    // Defines x.

声明使标识符(名称,如 x)已知。

定义创建一个object(例如int)。2定义也是一个声明,因为它使名称为人所知。

在同一个翻译单元(正在编译的源文件,及其所有包含的文件)中没有常规定义的暂定定义就像初始化为零的定义。

你应该正常使用这些的方式是:

  • 对于一个object,您将在多个文件中按名称访问,在一个源文件中只写一个定义。 (它可以是一个暂定定义3,如果你希望它被初始化为零,或者它可以是一个带有你选择的初始化器的常规定义。)
  • 在关联的 header 文件中(例如 foo.h 用于源文件 foo.c),使用 extern 声明名称,如上所示。
  • 在每个使用该名称的文件中包含 header 文件,包括其关联的源文件。 (后者很重要;当 foo.c 包含 foo.h 时,编译器将在同一编译中同时看到声明和定义,如果存在使两个声明不兼容的拼写错误,则会发出警告。 )

实际上,你正常使用它们的方式应该是完全不使用它们。程序通常不需要 object 的外部标识符,因此您应该在没有它们的情况下设计程序。以上规则适用于您何时使用它们。

暂定定义

在 Unix 和其他一些系统中,可以将暂定定义 int x; 放入 header 文件中,并将其包含在多个源文件中。由于暂定定义在没有常规定义的情况下就像定义一样,这导致多个翻译单元中存在多个定义。 C 标准没有定义 this 的行为。那么它在 Unix 中是如何工作的呢?

直到最近,当您使用 GCC(默认构建)进行编译时,它会创建一个 object 文件,该文件将临时定义的标识符标记为与常规定义的标识符不同。暂时定义的标识符被标记为“通用”。当链接器发现一个“公共”标识符的多个定义时,它会将它们合并为一个定义。请记住,C 标准没有定义行为。但是 Unix tools4 做到了。因此,您可以将 int x; 放在 header 中并将其包含在很多地方,并且在链接整个程序时您会从中得到一个 int x

在版本 10 及更高版本中,GCC 默认不执行此操作。在没有常规定义的情况下,暂定定义更像常规定义,并且与同一标识符的多个定义链接将导致错误,即使这些定义来自暂定定义。 GCC 切换到 select 旧行为,-fcommon

这是您应该了解的信息,以便您可以了解旧的源文件和 header利用“常见”行为的源文件。在新的源代码中不需要它,您应该只在 header 中编写 non-definition 声明(使用 extern),在源文件中编写常规定义。

其他

您不需要 extern 和函数声明,因为没有 body 的函数声明(包含函数代码的复合语句)自动成为声明,其行为与如果它有 extern。函数没有暂定定义。

脚注

1 此答案仅针对 object 标识符的外部声明和外部定义,以及外部链接。 C 声明的完整规则有些复杂,部分原因是 C 的演变历史。

2 这用于定义引用 object 的标识符。对于其他种类的标识符,定义什么可能不同。例如,据说 typedef int foofoo 定义为类型 int 的别名,但没有创建 object。

3 最好还包含一个初始值设定项,即使它是零,因为这将使它成为一个常规定义并避免出现相同名称的潜在问题在两个不同的源文件中对两个不同的事物使用了暂定定义,导致链接器没有抱怨,即使这是一个错误。

4 我可能对这里的术语草率了;一些body else 可以准确地识别指定此行为的位置以及它应用的工具。