介绍
There are three forms of declaration of concern here:1
extern int x; // Declares x but does not define it.
int x; // Tentative definition of x.
int x = 0; // Defines x.
声明产生一个标识符(一个名称,例如x
) known.
A definition creates an object (such as an int
).2 A definition is also a declaration, since it makes the name known.
在同一翻译单元(正在编译的源文件及其所有包含的文件)中没有常规定义的暂定定义就像初始值设定项为零的定义一样。
通常使用它们的方式应该是:
- For an object you will access by name in multiple files, write exactly one definition of it in one source file. (It can be a tentative definition3 if you would like it to be initialized with zero, or it can be a regular definition with an initializer you choose.)
- 在关联的头文件中(例如
foo.h
对于源文件foo.c
),声明名称,使用extern
如上图所示。
- 在使用该名称的每个文件中包含头文件,包括其关联的源文件。 (后者很重要;当
foo.c
包括foo.h
,编译器将在同一个编译中看到声明和定义,如果存在导致两个声明不兼容的拼写错误,则会向您发出警告。)
事实上,你正常使用它们的方式应该是根本不使用它们。程序通常不需要对象的外部标识符,因此您应该在没有它们的情况下设计程序。上述规则适用于您何时使用它们。
暂定定义
在Unix和其他一些系统中,已经可以给出一个暂定的定义,int x;
,在一个头文件中并将其包含在多个源文件中。由于暂定定义的作用类似于没有常规定义的定义,因此这会导致多个翻译单元中存在多个定义。 C 标准没有定义其行为。那么它在 Unix 中是如何工作的呢?
Until recently, when you compiled with GCC (as built by default), it created an object file that marked tentatively defined identifiers differently from regularly defined identifiers. The tentatively defined identifiers were marked as “common.” When the linker found multiple definitions of a “common” identifier, it coalesced them into a single definition. Remember, the C standard does not define the behavior. But Unix tools4 did. So you could put int x;
in a header and include it in lots of places, and you would get one int x
out of it when linking the entire program.
在版本 10 及更高版本中,GCC 默认情况下不执行此操作。在没有常规定义的情况下,临时定义更像是常规定义,并且与同一标识符的多个定义链接将导致错误,即使这些定义源自临时定义。 GCC 有一个选择旧行为的开关,-fcommon
.
这是您应该了解的信息,以便您可以理解利用“常见”行为的旧源文件和标头。新的源代码中不需要它,您应该只编写非定义声明(使用extern
) 在源文件的标头和常规定义中。
各种各样的
你不需要extern
带有函数声明,因为没有主体的函数声明(包含函数代码的复合语句)自动成为声明,并且其行为与具有函数声明相同extern
。函数没有暂定定义。
Footnote
1 This answer addresses only external declarations and external definitions for identifiers of objects, with external linkage. The full rules for C declarations are somewhat complicated, partly due to the history of C’s evolution.
2 This is for definitions of identifiers that refer to objects. For other kinds of identifiers, what is a definition may be different. For example, typedef int foo
is said to define foo
as an alias for the type int
, but no object is created.
3 It may be preferable to also include an initializer, even if it is zero, as this will make it a regular definition and avoid a potential problem where the same name is used tentative definitions in two different source files for two different things, resulting in the linker not complaining even though this is an error.
4 I may be being sloppy with terminology here; somebody else could identify precisely where this behavior was specified and what tools it applied to.