如何使用 extern 在源文件之间共享变量?
我知道 C 中的全局变量有时有 extern
关键字。什么是 extern
变量?声明是什么样的?其范围是什么?
这与跨源文件共享变量有关,但它是如何精确工作的呢?在哪里使用extern
?
I know that global variables in C sometimes have the extern
keyword. What is an extern
variable? What is the declaration like? What is its scope?
This is related to sharing variables across source files, but how does that work precisely? Where do I use extern
?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(19)
仅当您正在构建的程序时,使用
extern
才有意义由链接在一起的多个源文件组成,其中一些
例如,在源文件
file1.c
中定义的变量需要在其他源文件中引用,例如
file2.c
。了解定义
变量并声明 a
变量:
当编译器被告知某个变量时,该变量被声明
变量存在(这是它的类型);它不分配
此时变量的存储。
当编译器分配存储空间时,变量就被定义了
变量。
您可以多次声明一个变量(尽管一次就足够了);
在给定范围内只能定义一次。
变量定义也是声明,但不是所有变量
声明即定义。
声明和定义全局变量的最佳方法
声明和定义全局变量的干净、可靠的方法是使用
包含变量的
extern
声明 的头文件。标头包含在定义变量的一个源文件中
以及引用该变量的所有源文件。
对于每个程序,一个源文件(并且只有一个源文件)定义了
多变的。
类似地,一个头文件(并且只有一个头文件)应该声明
多变的。
头文件至关重要;它可以实现之间的交叉检查
独立的 TU(翻译单元 — 认为是源文件)并确保
一致性。
尽管还有其他方法,但此方法简单且有效
可靠的。
它由
file3.h
、file1.c
和file2.c
演示:file3.h
file1.c
file2.c
这是最好的方法声明和定义全局变量。
接下来的两个文件完成了
prog1
的源代码:显示的完整程序使用函数,因此函数声明具有
潜入。
C99 和 C11 都要求在调用函数之前先声明或定义函数
使用(而 C90 没有使用,有充分的理由)。
我在标头中的函数声明前面使用关键字
extern
为了一致性 - 匹配变量前面的
extern
标头中的声明。
许多人不喜欢在函数前面使用
extern
声明;编译器不在乎——最终,我也不在乎
只要你保持一致,至少在源文件中是一致的。
prog1.h
prog1.c
prog1
使用prog1.c
、file1.c
、file2.c
、file3.h
和prog1.h
。文件
prog1.mk
是仅用于prog1
的 makefile。它将与自本轮以来生成的大多数版本的
make
一起使用千年的。
它并不专门与 GNU Make 绑定。
prog1.mk
指南
只有专家才能打破的规则,并且只有在有充分理由的情况下才能打破:
头文件仅包含变量的
extern
声明 - 从不静态
或非限定变量定义。对于任何给定的变量,只有一个头文件声明它(SPOT —
单点事实)。
源文件从不包含变量的
extern
声明 —源文件始终包含声明它们的(唯一)标头。
对于任何给定的变量,只有一个源文件定义该变量,
最好也初始化它。 (虽然没有必要
明确初始化为零,它没有坏处并且可以做一些好事,
因为某一特定的初始化定义只能有一个
程序中的全局变量)。
定义变量的源文件还包含头文件
确保定义和声明一致。
函数永远不需要使用
extern
声明变量。尽可能避免全局变量 - 使用函数。
这个答案的源代码和文本可以在我的
SOQ(堆栈溢出问题)
GitHub 上的存储库位于
src/so-0143-3204
子目录。
如果您不是经验丰富的 C 程序员,您可以(也许
应该)停止阅读这里。
定义全局变量的方法不太好
使用一些(实际上是很多)C 编译器,您可以摆脱那些
也称为变量的“通用”定义。
这里的“Common”是指 Fortran 中用于共享的技术
源文件之间的变量,使用(可能命名的)COMMON 块。
这里发生的是,许多文件中的每一个都提供了一个暂定的
变量的定义。
只要不超过一个文件提供初始化定义,
那么各个文件最终共享一个共同的单一定义
变量:
file10.c
file11.c
file12.c
此技术不符合 C 标准的字母和
“一个定义规则”——这是正式的未定义行为:
然而,C 标准还在资料性附录 J 中将其列为
常用扩展。
由于该技术并不总是受支持,因此最好避免
使用它,特别是如果您的代码需要可移植。
使用这种技术,你也可能会得到无意的类型
双关语。
如果上述文件之一将
l
声明为double
而不是 along
,C 的类型不安全链接器可能不会发现不匹配。如果您使用的是 64 位
long
和double
的机器,您甚至不需要收到警告;在具有 32 位
long
和 64 位double
的机器上,您可能会收到有关不同大小的警告 - 链接器
将使用最大的大小,就像 Fortran 程序将使用
任何公共块的最大尺寸。
请注意,2020 年 5 月 7 日发布的 GCC 10.1.0 更改了
使用的默认编译选项
-fno-common
,这意味着默认情况下,上面的代码不再链接,除非您覆盖
默认为
-fcommon
(或使用属性等 - 请参阅链接)。接下来的两个文件完成了
prog2
的源代码:prog2.h
prog2.c
prog2
使用prog2.c
,file10.c
、file11.c
、file12.c
、prog2.h
。警告
正如此处的评论中所述,以及我对类似问题的回答中所述
问题,使用多个
全局变量的定义会导致未定义的行为(J.2;
§6.9),这是该标准表达“任何事情都可能发生”的方式。
可能发生的事情之一是程序的行为与您相同
预计; J.5.11 大约说,“你可能更幸运
比你应得的”。
但是依赖于外部变量的多个定义的程序
— 无论有或没有明确的“extern”关键字 — 都不是严格的
符合程序并不能保证在任何地方都能工作。
同样:它包含一个可能会或可能不会出现的错误。
违反准则
当然,可以通过多种方式违反这些准则。
有时,可能有充分的理由违反准则,但是
这种情况极为不寻常。
faulty_header.h
注 1:如果标头定义变量时不带
extern
关键字,然后包含标头的每个文件都会创建一个临时定义
变量的。
如前所述,这通常会起作用,但 C 标准却不能
保证它会起作用。
broken_header.h
注2:如果标头定义并初始化了变量,那么只有
给定程序中的一个源文件可以使用该标头。
由于标头主要用于共享信息,因此有点愚蠢
创建一个只能使用一次的产品。
Seldom_ Correct.h
注3:如果标头定义了静态变量(带或不带
初始化),然后每个源文件最终都有自己的私有文件
“全局”变量的版本。
例如,如果变量实际上是一个复杂的数组,这可能会导致
到极端重复的代码。偶尔,它可以是
达到某种效果的明智方法,但这是非常不寻常的。
总结
使用我首先展示的标题技术。
它在任何地方都能可靠地工作。
请特别注意,声明
global_variable
的标头是包含在使用它的每个文件中 - 包括定义它的文件。
这确保了一切都是自洽的。
声明和定义函数时也会出现类似的问题 -
类似的规则也适用。
但问题是具体关于变量的,所以我保留了
仅回答变量。
原始答案结束
如果您不是一位经验丰富的 C 程序员,您可能应该停止阅读此处。
后期主要添加
避免代码重复
一个问题有时(并且合法地)提出关于
描述了“标头中的声明,源代码中的定义”机制
这里有两个文件需要保持同步——标题
和来源。随后通常会观察到
可以使用宏,以便标头发挥双重作用 - 通常
声明变量,但是当在变量之前设置特定宏时
包含标头,它定义变量。
另一个问题可能是变量需要在每个中定义
一些“主要计划”。这通常是一种虚假的担忧。你
可以简单地引入一个C源文件来定义变量和链接
每个程序生成的目标文件。
典型的方案是这样工作的,使用原始的全局变量
如
file3.h
所示:file3a.h
file1a.c
file2a.c
接下来的两个文件完成
prog3
的源代码:prog3.h
prog3 .c
prog3
使用prog3.c
、file1a.c
、file2a.c
、file3a.h
,prog3.h
。变量初始化
如图所示,该方案的问题在于它没有提供
全局变量的初始化。使用 C99 或 C11 和可变参数
列出宏,您也可以定义一个宏来支持初始化。
(对于 C89 并且不支持宏中的变量参数列表,因此没有
处理任意长初始值设定项的简单方法。)
file3b.h
反转
#if
和#else
块的内容,修复由Denis Kniazhev
file1b.c
file2b.c
显然,奇怪结构的代码不是您想要的正常情况下
写,但它说明了这一点。第一个参数到第二个参数
INITIALIZER
的调用是{ 41
,其余参数(本例中为单数)是
43 }
。没有 C99 或类似的支持对于宏的变量参数列表,初始化器需要
包含逗号是非常有问题的。
包含正确的头文件
file3b.h
(而不是fileba.h
)Denis Kniazhev
接下来的两个文件完成了
prog4
:prog4.h
prog4.c
prog4
使用prog4.c
、file1b.c
、file2b.c< /code>、
prog4.h
、file3b.h
。标头防护
任何标头都应受到保护以防止重新包含,以便该类型
定义(枚举、结构或联合类型,或者通常称为 typedef)不
造成问题。标准技术是包裹身体
标头保护中的标头,例如:
标头可能会间接包含两次。例如,如果
file4b.h
包含用于未显示的类型定义的file3b.h
,并且
file1b.c
需要同时使用头文件file4b.h
和file3b.h
,然后您还有一些更棘手的问题需要解决。显然,你可以修改
仅包含
file4b.h
的标头列表。然而,你可能不是了解内部依赖关系 - 理想情况下,代码应该:
继续工作。
此外,它开始变得棘手,因为您可能包含
file4b.h
在包含
file3b.h
生成定义之前,但正常file3b.h
上的标头防护将防止标头被重新包含。因此,您最多需要包含
file3b.h
的主体一次声明,最多一次定义,但您可能两者都需要
在单个翻译单元(TU — 源文件和
它使用的标头)。
变量定义的多重包含
然而,它可以在不太不合理的约束下完成。
让我们引入一组新的文件名:
external.h
用于 EXTERN 宏定义等。file1c.h
用于定义类型(特别是struct oddball
,oddball_struct
)。file2c.h
定义或声明全局变量。file3c.c
定义全局变量。file4c.c
仅使用全局变量。file5c.c
显示您可以声明然后定义全局变量。file6c.c
显示您可以定义然后(尝试)声明全局变量。在这些示例中,
file5c.c
和file6c.c
直接包含标头file2c.h
多次,但这是表明该文件的最简单方法机制发挥作用。这意味着如果间接包含标题
两次,也就安全了。
其工作的限制是:
定义或声明全局变量的标头本身可能不是
定义任何类型。
在包含应定义变量的标头之前,
您定义宏 DEFINE_VARIABLES。
定义或声明变量的标头具有风格化的内容。
external.h
file1c.h
file2c.h
file3c.c
file4c.c
file5c.c
file6c.c
下一个源文件完成
prog5
的源代码(提供主程序),prog6
和prog7
:prog5.c
prog5
使用prog5.c
、file3c。 c
、file4c.c
、file1c.h
、file2c.h
、external.h
。prog6
使用prog5.c
、file5c.c
、file4c.c
、file1c。 h
、file2c.h
、external.h
。prog7
使用prog5.c
、file6c.c
、file4c.c
、file1c。 h
、file2c.h
、external.h
。该方案避免了大多数问题。只有在以下情况下您才会遇到问题
定义变量的标头(例如
file2c.h
)包含在另一个定义变量的头文件(例如
file7c.h
)。没有一个除了“不要这样做”之外,还有其他简单的方法可以解决这个问题。
您可以通过将
file2c.h
修改为部分解决该问题file2d.h
:file2d.h
问题变成“标头应该包含
#undef DEFINE_VARIABLES
吗?”如果您从标头中省略该内容并将任何定义调用包装为
#define
和#undef
:在源代码中(因此标头永远不会改变
DEFINE_VARIABLES
),那么你应该是干净的。这只是一个麻烦必须记住写下额外的行。另一种选择可能是:
externdef.h
这有点复杂,但似乎是安全的(使用
file2d.h
,file2d.h
中没有#undef DEFINE_VARIABLES
)。file7c.c
file8c.h
file8c.c
接下来的两个文件完成了
prog8
和prog9
的源代码:prog8.c
file9c.c
prog8
使用prog8.c
、file7c.c
、file9c.c
。prog9
使用prog8.c
、file8c.c
、file9c.c
。但实际中出现这些问题的可能性相对较小,
特别是如果您采用标准建议避免
全局变量,
此说明会遗漏任何内容吗?
_Confession_: The 'avoiding duplicated code' scheme outlined here was
developed because the issue affects some code I work on (but don't own),
and is a niggling concern with the scheme outlined in the first part of
the answer. However, the original scheme leaves you with just two
places to modify to keep variable definitions and declarations
synchronized, which is a big step forward over having exernal variable
declarations scattered throughout the code base (which really matters
when there are thousands of files in total). However, the code in the
files with the names `fileNc.[ch]` (plus `external.h` and `externdef.h`)
shows that it can be made to work. Clearly, it would not be hard to
create a header generator script to give you the standardized template
for a variable defining and declaring header file.
注意这些都是玩具程序,只有足够的代码来制作它们
有点有趣。示例中有重复之处
可以删除,但并不是为了简化教学解释。
(例如:
prog5.c
和prog8.c
之间的区别是名称包含的标头之一。有可能
重新组织代码,使
main()
函数不再重复,但是它隐藏的东西比它揭示的东西多。)
Using
extern
is only of relevance when the program you're buildingconsists of multiple source files linked together, where some of the
variables defined, for example, in source file
file1.c
need to bereferenced in other source files, such as
file2.c
.It is important to understand the difference between defining a
variable and declaring a
variable:
A variable is declared when the compiler is informed that a
variable exists (and this is its type); it does not allocate the
storage for the variable at that point.
A variable is defined when the compiler allocates the storage for
the variable.
You may declare a variable multiple times (though once is sufficient);
you may only define it once within a given scope.
A variable definition is also a declaration, but not all variable
declarations are definitions.
Best way to declare and define global variables
The clean, reliable way to declare and define global variables is to use
a header file to contain an
extern
declaration of the variable.The header is included by the one source file that defines the variable
and by all the source files that reference the variable.
For each program, one source file (and only one source file) defines the
variable.
Similarly, one header file (and only one header file) should declare the
variable.
The header file is crucial; it enables cross-checking between
independent TUs (translation units — think source files) and ensures
consistency.
Although there are other ways of doing it, this method is simple and
reliable.
It is demonstrated by
file3.h
,file1.c
andfile2.c
:file3.h
file1.c
file2.c
That's the best way to declare and define global variables.
The next two files complete the source for
prog1
:The complete programs shown use functions, so function declarations have
crept in.
Both C99 and C11 require functions to be declared or defined before they
are used (whereas C90 did not, for good reasons).
I use the keyword
extern
in front of function declarations in headersfor consistency — to match the
extern
in front of variabledeclarations in headers.
Many people prefer not to use
extern
in front of functiondeclarations; the compiler doesn't care — and ultimately, neither do I
as long as you're consistent, at least within a source file.
prog1.h
prog1.c
prog1
usesprog1.c
,file1.c
,file2.c
,file3.h
andprog1.h
.The file
prog1.mk
is a makefile forprog1
only.It will work with most versions of
make
produced since about the turnof the millennium.
It is not tied specifically to GNU Make.
prog1.mk
Guidelines
Rules to be broken by experts only, and only with good reason:
A header file only contains
extern
declarations of variables — neverstatic
or unqualified variable definitions.For any given variable, only one header file declares it (SPOT —
Single Point of Truth).
A source file never contains
extern
declarations of variables —source files always include the (sole) header that declares them.
For any given variable, exactly one source file defines the variable,
preferably initializing it too. (Although there is no need to
initialize explicitly to zero, it does no harm and can do some good,
because there can be only one initialized definition of a particular
global variable in a program).
The source file that defines the variable also includes the header to
ensure that the definition and the declaration are consistent.
A function should never need to declare a variable using
extern
.Avoid global variables whenever possible — use functions instead.
The source code and text of this answer are available in my
SOQ (Stack Overflow Questions)
repository on GitHub in the
src/so-0143-3204
sub-directory.
If you're not an experienced C programmer, you could (and perhaps
should) stop reading here.
Not so good way to define global variables
With some (indeed, many) C compilers, you can get away with what's
called a 'common' definition of a variable too.
'Common', here, refers to a technique used in Fortran for sharing
variables between source files, using a (possibly named) COMMON block.
What happens here is that each of a number of files provides a tentative
definition of the variable.
As long as no more than one file provides an initialized definition,
then the various files end up sharing a common single definition of the
variable:
file10.c
file11.c
file12.c
This technique does not conform to the letter of the C standard and the
'one definition rule' — it is officially undefined behaviour:
However, the C standard also lists it in informative Annex J as one of
the Common extensions.
Because this technique is not always supported, it is best to avoid
using it, especially if your code needs to be portable.
Using this technique, you can also end up with unintentional type
punning.
If one of the files above declared
l
as adouble
instead of as along
, C's type-unsafe linkers probably would not spot the mismatch.If you're on a machine with 64-bit
long
anddouble
, you'd not evenget a warning; on a machine with 32-bit
long
and 64-bitdouble
,you'd probably get a warning about the different sizes — the linker
would use the largest size, exactly as a Fortran program would take the
largest size of any common blocks.
Note that GCC 10.1.0, which was released on 2020-05-07, changes the
default compilation options to use
-fno-common
, which meansthat by default, the code above no longer links unless you override the
default with
-fcommon
(or use attributes, etc — see the link).The next two files complete the source for
prog2
:prog2.h
prog2.c
prog2
usesprog2.c
,file10.c
,file11.c
,file12.c
,prog2.h
.Warning
As noted in comments here, and as stated in my answer to a similar
question, using multiple
definitions for a global variable leads to undefined behaviour (J.2;
§6.9), which is the standard's way of saying "anything could happen".
One of the things that can happen is that the program behaves as you
expect; and J.5.11 says, approximately, "you might be lucky more often
than you deserve".
But a program that relies on multiple definitions of an extern variable
— with or without the explicit 'extern' keyword — is not a strictly
conforming program and not guaranteed to work everywhere.
Equivalently: it contains a bug which may or may not show itself.
Violating the guidelines
There are, of course, many ways in which these guidelines can be broken.
Occasionally, there may be a good reason to break the guidelines, but
such occasions are extremely unusual.
faulty_header.h
Note 1: if the header defines the variable without the
extern
keyword,then each file that includes the header creates a tentative definition
of the variable.
As noted previously, this will often work, but the C standard does not
guarantee that it will work.
broken_header.h
Note 2: if the header defines and initializes the variable, then only
one source file in a given program can use the header.
Since headers are primarily for sharing information, it is a bit silly
to create one that can only be used once.
seldom_correct.h
Note 3: if the header defines a static variable (with or without
initialization), then each source file ends up with its own private
version of the 'global' variable.
If the variable is actually a complex array, for example, this can lead
to extreme duplication of code. It can, very occasionally, be a
sensible way to achieve some effect, but that is very unusual.
Summary
Use the header technique I showed first.
It works reliably and everywhere.
Note, in particular, that the header declaring the
global_variable
isincluded in every file that uses it — including the one that defines it.
This ensures that everything is self-consistent.
Similar concerns arise with declaring and defining functions —
analogous rules apply.
But the question was about variables specifically, so I've kept the
answer to variables only.
End of Original Answer
If you're not an experienced C programmer, you probably should stop reading here.
Late Major Addition
Avoiding Code Duplication
One concern that is sometimes (and legitimately) raised about the
'declarations in headers, definitions in source' mechanism described
here is that there are two files to be kept synchronized — the header
and the source. This is usually followed up with an observation that a
macro can be used so that the header serves double duty — normally
declaring the variables, but when a specific macro is set before the
header is included, it defines the variables instead.
Another concern can be that the variables need to be defined in each of
a number of 'main programs'. This is normally a spurious concern; you
can simply introduce a C source file to define the variables and link
the object file produced with each of the programs.
A typical scheme works like this, using the original global variable
illustrated in
file3.h
:file3a.h
file1a.c
file2a.c
The next two files complete the source for
prog3
:prog3.h
prog3.c
prog3
usesprog3.c
,file1a.c
,file2a.c
,file3a.h
,prog3.h
.Variable initialization
The problem with this scheme as shown is that it does not provide for
initialization of the global variable. With C99 or C11 and variable argument
lists for macros, you could define a macro to support initialization too.
(With C89 and no support for variable argument lists in macros, there is no
easy way to handle arbitrarily long initializers.)
file3b.h
Reverse contents of
#if
and#else
blocks, fixing bug identified byDenis Kniazhev
file1b.c
file2b.c
Clearly, the code for the oddball structure is not what you'd normally
write, but it illustrates the point. The first argument to the second
invocation of
INITIALIZER
is{ 41
and the remaining argument(singular in this example) is
43 }
. Without C99 or similar supportfor variable argument lists for macros, initializers that need to
contain commas are very problematic.
Correct header
file3b.h
included (instead offileba.h
) perDenis Kniazhev
The next two files complete the source for
prog4
:prog4.h
prog4.c
prog4
usesprog4.c
,file1b.c
,file2b.c
,prog4.h
,file3b.h
.Header Guards
Any header should be protected against reinclusion, so that type
definitions (enum, struct or union types, or typedefs generally) do not
cause problems. The standard technique is to wrap the body of the
header in a header guard such as:
The header might be included twice indirectly. For example, if
file4b.h
includesfile3b.h
for a type definition that isn't shown,and
file1b.c
needs to use both headerfile4b.h
andfile3b.h
, thenyou have some more tricky issues to resolve. Clearly, you might revise
the header list to include just
file4b.h
. However, you might not beaware of the internal dependencies — and the code should, ideally,
continue to work.
Further, it starts to get tricky because you might include
file4b.h
before including
file3b.h
to generate the definitions, but the normalheader guards on
file3b.h
would prevent the header being reincluded.So, you need to include the body of
file3b.h
at most once fordeclarations, and at most once for definitions, but you might need both
in a single translation unit (TU — a combination of a source file and
the headers it uses).
Multiple inclusion with variable definitions
However, it can be done subject to a not too unreasonable constraint.
Let's introduce a new set of file names:
external.h
for the EXTERN macro definitions, etc.file1c.h
to define types (notably,struct oddball
, the type ofoddball_struct
).file2c.h
to define or declare the global variables.file3c.c
which defines the global variables.file4c.c
which simply uses the global variables.file5c.c
which shows that you can declare and then define the global variables.file6c.c
which shows that you can define and then (attempt to) declare the global variables.In these examples,
file5c.c
andfile6c.c
directly include the headerfile2c.h
several times, but that is the simplest way to show that themechanism works. It means that if the header was indirectly included
twice, it would also be safe.
The restrictions for this to work are:
The header defining or declaring the global variables may not itself
define any types.
Immediately before you include a header that should define variables,
you define the macro DEFINE_VARIABLES.
The header defining or declaring the variables has stylized contents.
external.h
file1c.h
file2c.h
file3c.c
file4c.c
file5c.c
file6c.c
The next source file completes the source (provides a main program) for
prog5
,prog6
andprog7
:prog5.c
prog5
usesprog5.c
,file3c.c
,file4c.c
,file1c.h
,file2c.h
,external.h
.prog6
usesprog5.c
,file5c.c
,file4c.c
,file1c.h
,file2c.h
,external.h
.prog7
usesprog5.c
,file6c.c
,file4c.c
,file1c.h
,file2c.h
,external.h
.This scheme avoids most problems. You only run into a problem if a
header that defines variables (such as
file2c.h
) is included byanother header (say
file7c.h
) that defines variables. There isn't aneasy way around that other than "don't do it".
You can partially work around the problem by revising
file2c.h
intofile2d.h
:file2d.h
The issue becomes 'should the header include
#undef DEFINE_VARIABLES
?'If you omit that from the header and wrap any defining invocation with
#define
and#undef
:in the source code (so the headers never alter the value of
DEFINE_VARIABLES
), then you should be clean. It is just a nuisance tohave to remember to write the the extra line. An alternative might be:
externdef.h
This is getting a tad convoluted, but seems to be secure (using the
file2d.h
, with no#undef DEFINE_VARIABLES
in thefile2d.h
).file7c.c
file8c.h
file8c.c
The next two files complete the source for
prog8
andprog9
:prog8.c
file9c.c
prog8
usesprog8.c
,file7c.c
,file9c.c
.prog9
usesprog8.c
,file8c.c
,file9c.c
.However, the problems are relatively unlikely to occur in practice,
especially if you take the standard advice to
Avoid global variables
Does this exposition miss anything?
_Confession_: The 'avoiding duplicated code' scheme outlined here was
developed because the issue affects some code I work on (but don't own),
and is a niggling concern with the scheme outlined in the first part of
the answer. However, the original scheme leaves you with just two
places to modify to keep variable definitions and declarations
synchronized, which is a big step forward over having exernal variable
declarations scattered throughout the code base (which really matters
when there are thousands of files in total). However, the code in the
files with the names `fileNc.[ch]` (plus `external.h` and `externdef.h`)
shows that it can be made to work. Clearly, it would not be hard to
create a header generator script to give you the standardized template
for a variable defining and declaring header file.
NB These are toy programs with just barely enough code to make them
marginally interesting. There is repetition within the examples that
could be removed, but isn't to simplify the pedagogical explanation.
(For example: the difference between
prog5.c
andprog8.c
is the nameof one of the headers that are included. It would be possible to
reorganize the code so that the
main()
function was not repeated, butit would conceal more than it revealed.)
extern
变量是在另一个翻译单元中定义的变量的声明(感谢 sbi 的更正)。这意味着变量的存储空间分配在另一个文件中。假设您有两个
.c
文件:test1.c
和test2.c
。如果您在test1.c
中定义了一个全局变量int test1_var;
并且您想在test2.c
中访问此变量,则必须在test2.c
中使用extern int test1_var;
。完整样本:
An
extern
variable is a declaration (thanks to sbi for the correction) of a variable which is defined in another translation unit. That means the storage for the variable is allocated in another file.Say you have two
.c
-filestest1.c
andtest2.c
. If you define a global variableint test1_var;
intest1.c
and you'd like to access this variable intest2.c
you have to useextern int test1_var;
intest2.c
.Complete sample:
Extern 是用于声明变量本身驻留在另一个翻译单元中的关键字。
因此,您可以决定在翻译单元中使用变量,然后从另一个翻译单元访问它,然后在第二个翻译单元中将其声明为 extern,并且该符号将由链接器解析。
如果您不将其声明为 extern,您将得到两个名称相同但完全不相关的变量,以及该变量的多个定义的错误。
Extern is the keyword you use to declare that the variable itself resides in another translation unit.
So you can decide to use a variable in a translation unit and then access it from another one, then in the second one you declare it as extern and the symbol will be resolved by the linker.
If you don't declare it as extern you'll get 2 variables named the same but not related at all, and an error of multiple definitions of the variable.
声明不会分配内存(必须为内存分配定义变量),但定义会。
这只是 extern 关键字的另一个简单视图,因为其他答案确实很棒。
Declaration won't allocate memory (the variable must be defined for memory allocation) but the definition will.
This is just another simple view on the extern keyword since the other answers are really great.
我喜欢将外部变量视为您对编译器做出的承诺。
当遇到 extern 时,编译器只能找出它的类型,而不能找出它“所在”的位置,因此无法解析引用。
你告诉它,“相信我。在链接时这个引用将是可解析的。”
I like to think of an extern variable as a promise that you make to the compiler.
When encountering an extern, the compiler can only find out its type, not where it "lives", so it can't resolve the reference.
You are telling it, "Trust me. At link time this reference will be resolvable."
添加
extern
将变量定义转变为变量声明。请参阅此线程了解声明和定义之间的区别。Adding an
extern
turns a variable definition into a variable declaration. See this thread as to what's the difference between a declaration and a definition.extern 告诉编译器相信您该变量的内存是在其他地方声明的,因此它不会尝试分配/检查内存。
因此,您可以编译一个引用 extern 的文件,但如果该内存未在某处声明,则无法链接。
对于全局变量和库很有用,但很危险,因为链接器不进行类型检查。
extern tells the compiler to trust you that the memory for this variable is declared elsewhere, so it doesnt try to allocate/check memory.
Therefore, you can compile a file that has reference to an extern, but you can not link if that memory is not declared somewhere.
Useful for global variables and libraries, but dangerous because the linker does not type check.
extern 的正确解释是你告诉编译器一些东西。您告诉编译器,尽管现在不存在,但声明的变量将以某种方式被链接器找到(通常在另一个对象(文件)中)。然后,链接器将很幸运地找到所有内容并将其组合在一起,无论您是否有一些外部声明。
The correct interpretation of extern is that you tell something to the compiler. You tell the compiler that, despite not being present right now, the variable declared will somehow be found by the linker (typically in another object (file)). The linker will then be the lucky guy to find everything and put it together, whether you had some extern declarations or not.
GCC ELF Linux实现
其他答案已经涵盖了语言使用方面的观点,所以现在让我们看一下在这个实现中它是如何实现的。
main.c
编译和反编译:
输出包含:
System V ABI更新ELF规范“符号表”章节解释:
这基本上是 C 标准赋予
extern
变量的行为。从现在开始,链接器的工作就是生成最终的程序,但是
extern
信息已经从源代码中提取到目标文件中。在 GCC 4.8 上测试。
C++17 内联变量
在 C++17 中,您可能希望使用内联变量而不是外部变量,因为它们使用简单(只需在标头中定义一次)并且功能更强大(支持 constexpr)。请参阅:“const static”是什么意思C 和 C++?
GCC ELF Linux implementation
Other answers have covered the language usage side of view, so now let's have a look at how it is implemented in this implementation.
main.c
Compile and decompile:
Output contains:
The System V ABI Update ELF spec "Symbol Table" chapter explains:
which is basically the behavior the C standard gives to
extern
variables.From now on, it is the job of the linker to make the final program, but the
extern
information has already been extracted from the source code into the object file.Tested on GCC 4.8.
C++17 inline variables
In C++17, you might want to use inline variables instead of extern ones, as they are simple to use (can be defined just once on header) and more powerful (support constexpr). See: What does 'const static' mean in C and C++?
extern 关键字与变量一起使用,以将其标识为全局变量。
extern keyword is used with the variable for its identification as a global variable.
在 C 语言中,文件 example.c 中的变量被赋予局部作用域。编译器期望变量在同一个文件 example.c 中具有其定义,当它没有找到相同的变量时,它将抛出一个错误。另一方面,函数默认具有全局作用域。因此,您不必明确向编译器提及“看哥们……您可能会在这里找到此函数的定义”。对于包含其声明的文件的函数就足够了。(您实际上称为头文件的文件)。
例如,考虑以下 2 个文件:
example.c
example1.c
现在,使用以下命令将两个文件一起编译:
step 1)cc -o ex example.c example1.c
步骤 2)./ex
您将得到以下输出: The value of a is <5>
In C a variable inside a file say example.c is given local scope. The compiler expects that the variable would have its definition inside the same file example.c and when it does not find the same , it would throw an error.A function on the other hand has by default global scope . Thus you do not have to explicitly mention to the compiler "look dude...you might find the definition of this function here". For a function including the file which contains its declaration is enough.(The file which you actually call a header file).
For example consider the following 2 files :
example.c
example1.c
Now when you compile the two files together, using the following commands :
step 1)cc -o ex example.c example1.c
step 2)./ex
You get the following output : The value of a is <5>
外部
允许程序的一个模块访问在程序的另一个模块中声明的全局变量或函数。
通常在头文件中声明外部变量。
如果您不希望程序访问您的变量或函数,您可以使用
static
,它告诉编译器该变量或函数不能在该模块之外使用。extern
allows one module of your program to access a global variable or function declared in another module of your program.
You usually have extern variables declared in header files.
If you don't want a program to access your variables or functions, you use
static
which tells the compiler that this variable or function cannot be used outside of this module.首先,
extern
关键字不用于定义变量;相反,它用于声明变量。我可以说 extern 是一个存储类,而不是一种数据类型。extern
用于让其他 C 文件或外部组件知道该变量已在某处定义。示例:如果您正在构建一个库,则无需在库本身的某个位置强制定义全局变量。该库将直接编译,但在链接文件时,它会检查定义。First off, the
extern
keyword is not used for defining a variable; rather it is used for declaring a variable. I can sayextern
is a storage class, not a data type.extern
is used to let other C files or external components know this variable is already defined somewhere. Example: if you are building a library, no need to define global variable mandatorily somewhere in library itself. The library will be compiled directly, but while linking the file, it checks for the definition.extern
只是意味着变量在其他地方定义(例如,在另一个文件中)。extern
simply means a variable is defined elsewhere (e.g., in another file).使用
extern
是为了使一个first.c
文件可以完全访问另一个second.c
文件中的全局参数。extern
可以在first.c
文件或first.c
包含的任何头文件中声明。extern
is used so onefirst.c
file can have full access to a global parameter in anothersecond.c
file.The
extern
can be declared in thefirst.c
file or in any of the header filesfirst.c
includes.使用 xc8 你必须小心声明变量
尽可能在每个文件中使用相同的类型,但错误的是,
在一个文件中声明一个
int
值,在另一个文件中声明一个char
值。这可能会导致变量损坏。
大约 15 年前,这个问题在一个微芯片论坛上得到了优雅的解决
/* 请参阅“http:www.htsoft.com”/
/“forum/all/showflat.php/Cat/0/Number/18766/an/0/page/0#18766”
但是这个链接似乎不再起作用......
所以我会很快尝试解释一下;
创建一个名为 global.h 的文件。
在其中声明以下内容
现在在文件 main.c 中
这意味着在 main.c 中变量将被声明为
unsigned char
。现在在其他文件中只需包含 global.h 即可
将其声明为该文件的外部。
但它会被正确声明为
unsigned char
。旧论坛帖子可能对此进行了更清楚的解释。
但这是使用编译器时真正潜在的
陷阱
它允许您在一个文件中声明一个变量,然后在另一个文件中将其声明为 extern 作为不同的类型。相关问题
也就是说,如果您说在另一个文件中将testing_mode声明为int
它会认为它是一个 16 位 var 并覆盖 ram 的其他部分,可能会破坏另一个变量。调试困难!
With xc8 you have to be careful about declaring a variable
as the same type in each file as you could , erroneously,
declare something an
int
in one file and achar
say in another.This could lead to corruption of variables.
This problem was elegantly solved in a microchip forum some 15 years ago
/* See "http:www.htsoft.com" /
/ "forum/all/showflat.php/Cat/0/Number/18766/an/0/page/0#18766"
But this link seems to no longer work...
So I;ll quickly try to explain it;
make a file called global.h.
In it declare the following
Now in the file main.c
This means in main.c the variable will be declared as an
unsigned char
.Now in other files simply including global.h will
have it declared as an extern for that file.
But it will be correctly declared as an
unsigned char
.The old forum post probably explained this a bit more clearly.
But this is a real potential
gotcha
when using a compilerthat allows you to declare a variable in one file and then declare it extern as a different type in another. The problems associated with
that are if you say declared testing_mode as an int in another file
it would think it was a 16 bit var and overwrite some other part of ram, potentially corrupting another variable. Difficult to debug!
简而言之,
extern
意味着变量在其他模块中定义,并且其地址在链接时已知。编译器不会在当前模块中保留内存,并且知道变量类型。要理解 extern,至少要有一点汇编经验是有好处的。In short
extern
means that variable is defined in other module and its address will be known at link time. The compiler does not reserve memory in current module and knows the variable type. To understandextern
is good to have at least little experience with assembler.符号(var 或函数)之前的 extern 关键字告诉链接器它(源文件)使用外部符号。这可以通过在这样的目标文件(.o)上运行 nm -a 来看到,该文件使用或分配一个值给 extern var(记住在顶部声明一个 extern 符号,如 extern int x 或者更好,使用头文件在 var 和函数之前有 extern;然后在 main 中给它赋值,就像这样 x=5;),我发现针对这样的 extern var(符号)未定义的 bss 信息(写字母 B)。这意味着 x 仍未解析,并将在 ld 运行时(在链接时)解析。
为什么总是在标头中使用 extern ?
如果我不使用 extern,只需声明 int x,该声明就会变得有点强并且没有 extern,并且这会在包含标头的每个源中重新定义相同的变量,从而有效地隐藏原始变量。因此,只需在 ah 标头中 int x ,我就在包含此 ah 的每个源中重新定义一个新的全局变量 x 源中的此 var,标头阴影中的此无 extern var decl (它并不完全是阴影,它正在重新定义一个全局变量)每个源代码中的变量 x 仅包含 int x 的标头,没有 extern,当我包含此类标头并尝试从此类文件编译 .o 时,每个 .o 都有自己的全局变量 x 定义,该定义包含在没有 extern 的标头,并且在链接时,我收到错误:变量或符号 x) 在源文件中其他位置定义的重要变量的多重定义。
重要的!头文件中的 vars 之前必须使用 extern。
默认情况下,函数已经是外部的。
extern keyword before a symbol (a var or function) tells the linker that it(the source file) uses an external symbol. This can be seen by running nm -a on such an object file (.o) which uses or assigns a value to a extern var (remember to declare a extern symbol on top like this extern int x or still better, use a header file with extern before vars and functions can be without extern; then in main assign a value to it like this x=5;), i find undefined bss info (letter B written) against such an extern var(symbol). This means x is still unresolved and will be resolved when ld is run (during link-time).
why always use extern in headers?
If i don't use extern, just declare int x, the declaration becomes sort-of strong and without extern, and this redifines the same variable in every source that includes the header, effectively shadowing the original variable. therefore with just int x in a.h header, I redefine a new global variable x in every source that include this a.h. This var in the source, this without-extern var decl in headers shadows(it doesn't shadow exactly, it's redifining a global variable x in every source code that includes the header with just int x, without extern, when i include such header and try to compile .o from such files, every .o has its own definition of this global variable x which was included in the header without extern, and at the time of linking, I get the error multiple definition of variable or symbol x) an important variable defined somewhere of somewhere else in the source files.
Important! it is necessary to use extern before vars in headers.
Functions are already extern by-default.
我使用一个非常简短的解决方案来允许头文件包含对象的外部引用或实际实现。实际包含该对象的文件只是执行
#define GLOBAL_FOO_IMPLMENTATION
。然后,当我向该文件添加新对象时,它也会显示在该文件中,而无需复制和粘贴定义。我在多个文件中使用这种模式。因此,为了使事情尽可能独立,我只是重用单个 GLOBAL & 。每个标头中的 GLOBALINIT 宏。我的标题看起来像这样:
A very short solution I use to allow a header file to contain the extern reference or actual implementation of an object. The file that actually contains the object just does
#define GLOBAL_FOO_IMPLEMENTATION
. Then when I add a new object to this file it shows up in that file also without me having to copy and paste the definition.I use this pattern across multiple files. So in order to keep things as self contained as possible, I just reuse the single GLOBAL & GLOBALINIT macros in each header. My headers look like this: