如何使用extern来共享源文件之间的变量?

时间:2022-09-07 21:09:17

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?

我知道C中的全局变量有时有extern关键字。什么是外部变量?宣言是什么样子的?它的范围是什么?

This is related to sharing variables across source files, but how does that work precisely? Where do I use extern?

这与在源文件*享变量有关,但是如何精确地工作呢?我在哪里使用extern?

15 个解决方案

#1


1467  

Using extern is only of relevance when the program you're building consists of multiple source files linked together, where some of the variables defined, for example, in source file file1.c need to be referenced in other source files, such as file2.c.

当您正在构建的程序由多个链接在一起的源文件组成时,使用extern只具有相关性,例如,源代码文件1中定义了一些变量。c需要在其他源文件中引用,例如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.

头由定义变量的源文件和引用变量的所有源文件包含。对于每个程序,一个源文件(只有一个源文件)定义变量。类似地,一个头文件(只有一个头文件)应该声明变量。头文件至关重要;它允许在独立的TUs(翻译单位-考虑源文件)之间进行交叉检查,并确保一致性。

Although there are other ways of doing it, this method is simple and reliable. It is demonstrated by file3.h, file1.c and file2.c:

虽然有其他的方法,但这个方法简单可靠。它由file3演示。h,file1。c和file2.c:

file3.h

extern int global_variable;  /* Declaration of the variable */

file1.c

#include "file3.h"  /* Declaration made available here */
#include "prog1.h"  /* Function declarations */

/* Variable defined here */
int global_variable = 37;    /* Definition checked against declaration */

int increment(void) { return global_variable++; }

file2.c

#include "file3.h"
#include "prog1.h"
#include <stdio.h>

void use_it(void)
{
    printf("Global variable: %d\n", global_variable++);
}

That's the best way to declare and define global variables.

这是声明和定义全局变量的最佳方式。


The next two files complete the source for prog1:

接下来的两个文件完成了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 headers for consistency — to match the extern in front of variable declarations in headers. Many people prefer not to use extern in front of function declarations; the compiler doesn't care — and ultimately, neither do I as long as you're consistent, at least within a source file.

完整的程序显示了使用函数,因此函数声明已经进入。C99和C11都要求在使用函数之前声明或定义函数(而C90没有声明函数,这是有充分理由的)。为了保持一致性,我在header中的函数声明前使用了关键字extern——为了匹配header中变量声明前的extern。许多人不喜欢在函数声明前使用extern;编译器不关心——而且最终,只要您是一致的,我也不关心,至少在一个源文件中。

prog1.h

extern void use_it(void);
extern int increment(void);

prog1.c

#include "file3.h"
#include "prog1.h"
#include <stdio.h>

int main(void)
{
    use_it();
    global_variable += 19;
    use_it();
    printf("Increment: %d\n", increment());
    return 0;
}
  • prog1 uses prog1.c, file1.c, file2.c, file3.h and prog1.h.
  • prog1使用prog1。c,file1。c、file2。c,file3。h和prog1.h。

The file prog1.mk is a makefile for prog1 only. It will work with most versions of make produced since about the turn of the millennium. It is not tied specifically to GNU Make.

文件prog1。mk是只用于prog1的makefile。从世纪之交开始,它将与大多数版本的产品合作。它不是专门与GNU Make绑定的。

prog1.mk

# Minimal makefile for prog1

PROGRAM = prog1
FILES.c = prog1.c file1.c file2.c
FILES.h = prog1.h file3.h
FILES.o = ${FILES.c:.c=.o}

CC      = gcc
SFLAGS  = -std=c11
GFLAGS  = -g
OFLAGS  = -O3
WFLAG1  = -Wall
WFLAG2  = -Wextra
WFLAG3  = -Werror
WFLAG4  = -Wstrict-prototypes
WFLAG5  = -Wmissing-prototypes
WFLAGS  = ${WFLAG1} ${WFLAG2} ${WFLAG3} ${WFLAG4} ${WFLAG5}
UFLAGS  = # Set on command line only

CFLAGS  = ${SFLAGS} ${GFLAGS} ${OFLAGS} ${WFLAGS} ${UFLAGS}
LDFLAGS =
LDLIBS  =

all:    ${PROGRAM}

${PROGRAM}: ${FILES.o}
    ${CC} -o $@ ${CFLAGS} ${FILES.o} ${LDFLAGS} ${LDLIBS}

prog1.o: ${FILES.h}
file1.o: ${FILES.h}
file2.o: ${FILES.h}

# If it exists, prog1.dSYM is a directory on macOS
DEBRIS = a.out core *~ *.dSYM
RM_FR  = rm -fr

clean:
    ${RM_FR} ${FILES.o} ${PROGRAM} ${DEBRIS}

Guidelines

Rules to be broken by experts only, and only with good reason:

只有专家才能打破这些规则,而且必须有充分的理由:

  • A header file only contains extern declarations of variables — never static or unqualified variable definitions.
  • 头文件只包含变量的extern声明—从不静态或非限定变量定义。
  • 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.

这个答案的源代码和文本可以在src/so-0143-3204子目录中GitHub上的SOQ(堆栈溢出问题)存储库中找到。

If you're not an experienced C programmer, you could (and perhaps should) stop reading here.

如果你不是一个有经验的C程序员,你可以(也许应该)停止在这里阅读。

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:

有了一些(实际上是许多)C编译器,您也可以不受所谓的变量“通用”定义的影响。这里的“Common”指的是Fortran语言中使用的一种技术,它使用(可能命名的)公共块在源文件之间共享变量。这里发生的是,许多文件中的每一个都提供了变量的暂定定义。只要只有一个文件提供了初始化的定义,那么不同的文件最终会共享一个通用的单一变量定义:

file10.c

#include "prog2.h"

int i;   /* Do not do this in portable code */

void inc(void) { i++; }

file11.c

#include "prog2.h"

int i;   /* Do not do this in portable code */

void dec(void) { i--; }

file12.c

#include "prog2.h"
#include <stdio.h>

int i = 9;   /* Do not do this in portable code */

void put(void) { printf("i = %d\n", i); }

This technique does not conform to the letter of the C standard and the 'one definition rule', but the C standard lists it as a common variation on its one definition rule.

这种技术不符合C标准的字母和“一个定义规则”,但是C标准将它列为其一个定义规则的常见变体。

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 declared i as a double instead of as an int, C's type-unsafe linkers probably would not spot the mismatch. If you're on a machine with 64-bit int and double, you'd not even get a warning; on a machine with 32-bit int and 64-bit double, 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.

由于不总是支持这种技术,所以最好避免使用它,尤其是当代码需要可移植时。使用这种技术,您还可以使用无意类型punning。如果其中一个文件将i声明为double,而不是int,那么C的不安全类型链接器可能不会发现不匹配。如果您在一台具有64位int和双精度的机器上,您甚至不会得到警告;在一台32位int和64位double的机器上,您可能会得到关于不同大小的警告——链接器将使用最大的大小,就像Fortran程序将使用任何公共块中最大的大小一样。

This is mentioned in the C standard in informative Annex J as a common extension:

在附录J中,C标准中提到了这一点:

J.5.11 Multiple external definitions

J.5.11多个外部定义

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).

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


The next two files complete the source for prog2:

接下来的两个文件完成了prog2的源代码:

prog2.h

extern void dec(void);
extern void put(void);
extern void inc(void);

prog2.c

#include "prog2.h"
#include <stdio.h>

int main(void)
{
    inc();
    put();
    dec();
    put();
    dec();
    put();
}
  • prog2 uses prog2.c, file10.c, file11.c, file12.c, prog2.h.
  • prog2使用prog2。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, 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.

正如这里的注释中所指出的,正如我对类似问题的回答中所述,对全局变量使用多个定义会导致未定义的行为,这是“任何事情都可能发生”的标准说法。可能发生的一件事是程序按照你的期望运行;而J.5.11说,“你可能比你应得的更幸运”。但是依赖于一个外部变量的多个定义的程序——带有或不带有显式的“extern”关键字——并不是一个严格遵循的程序,并且不能保证在任何地方都能正常运行。等价地说:它包含一个可能显示自己也可能不显示自己的错误。

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

int some_var;    /* Do not do this in a header!!! */

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.

注意1:如果header定义了变量,但没有使用extern关键字,那么包含header的每个文件都创建了变量的临时定义。如前所述,这通常会起作用,但是C标准并不能保证它会起作用。

broken_header.h

int some_var = 13;    /* Only one source file in a program can use this */

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.

注意2:如果头文件定义并初始化变量,那么给定程序中只有一个源文件可以使用头文件。由于头文件主要用于共享信息,所以创建一个只能使用一次的头文件有点愚蠢。

seldom_correct.h

static int hidden_global = 3;   /* Each source file gets its own copy  */

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.

注意3:如果标头定义了一个静态变量(有或没有初始化),那么每个源文件最后都有自己的“全局”变量的私有版本。

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 is included in every file that uses it — including the one that defines it. This ensures that everything is self-consistent.

使用我首先展示的头部技术。它在任何地方都可靠地工作。特别要注意的是,声明global_variable的头包含在每个使用它的文件中——包括定义它的文件。这确保了一切都是自洽的。

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.

如果你不是一个有经验的C程序员,你应该停止阅读这里。


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.

另一个问题可能是需要在许多“主程序”中定义变量。这通常是虚假的担忧;您可以简单地引入一个C源文件来定义变量,并将每个程序生成的对象文件链接起来。

A typical scheme works like this, using the original global variable illustrated in file3.h:

典型的方案是这样工作的,使用file3.h中所示的原始全局变量:

file3a.h

#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#else
#define EXTERN extern
#endif /* DEFINE_VARIABLES */

EXTERN int global_variable;

file1a.c

#define DEFINE_VARIABLES
#include "file3a.h"  /* Variable defined - but not initialized */
#include "prog3.h"

int increment(void) { return global_variable++; }

file2a.c

#include "file3a.h"
#include "prog3.h"
#include <stdio.h>

void use_it(void)
{
    printf("Global variable: %d\n", global_variable++);
}

The next two files complete the source for prog3:

接下来的两个文件完成了prog3的源代码:

prog3.h

extern void use_it(void);
extern int increment(void);

prog3.c

#include "file3a.h"
#include "prog3.h"
#include <stdio.h>

int main(void)
{
    use_it();
    global_variable += 19;
    use_it();
    printf("Increment: %d\n", increment());
    return 0;
}
  • prog3 uses prog3.c, file1a.c, file2a.c, file3a.h, prog3.h.
  • prog3使用prog3。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.)

这个方案的问题在于它没有提供全局变量的初始化。使用C99或C11以及宏的变量参数列表,您可以定义一个支持初始化的宏。(C89不支持宏中的变量参数列表,因此无法轻松处理任意长的初始化。)

file3b.h

#ifdef DEFINE_VARIABLES
#define EXTERN                  /* nothing */
#define INITIALIZER(...)        = __VA_ARGS__
#else
#define EXTERN                  extern
#define INITIALIZER(...)        /* nothing */
#endif /* DEFINE_VARIABLES */

EXTERN int global_variable INITIALIZER(37);
EXTERN struct { int a; int b; } oddball_struct INITIALIZER({ 41, 43 });

Reverse contents of #if and #else blocks, fixing bug identified by Denis Kniazhev

#if和#else块的反向内容,由Denis Kniazhev确定的修复bug。

file1b.c

#define DEFINE_VARIABLES
#include "file3b.h"  /* Variables now defined and initialized */
#include "prog4.h"

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

file2b.c

#include "file3b.h"
#include "prog4.h"
#include <stdio.h>

void use_them(void)
{
    printf("Global variable: %d\n", global_variable++);
    oddball_struct.a += global_variable;
    oddball_struct.b -= global_variable / 2;
}

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 support for variable argument lists for macros, initializers that need to contain commas are very problematic.

显然,古怪结构的代码不是您通常编写的,但它说明了问题所在。第二次初始化器调用的第一个参数是{41,其余的参数(本例中的单数)是43}。如果没有C99或类似的对宏的变量参数列表的支持,需要包含逗号的初始化器就会很成问题。

Correct header file3b.h included (instead of fileba.h) per Denis Kniazhev

正确的标题file3b。h包括(而不是fileba.h)每个Denis Kniazhev。


The next two files complete the source for prog4:

接下来的两个文件完成了prog4的源代码:

prog4.h

extern int increment(void);
extern int oddball_value(void);
extern void use_them(void);

prog4.c

#include "file3b.h"
#include "prog4.h"
#include <stdio.h>

int main(void)
{
    use_them();
    global_variable += 19;
    use_them();
    printf("Increment: %d\n", increment());
    printf("Oddball:   %d\n", oddball_value());
    return 0;
}
  • prog4 uses prog4.c, file1b.c, file2b.c, prog4.h, file3b.h.
  • prog4使用prog4。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:

任何标头都应该受到保护,以防止重新包含,以便类型定义(枚举、结构或联合类型,或通常的typedef)不会导致问题。标准的技术是将头部的主体包裹在头部防护中,例如:

#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED

...contents of header...

#endif /* FILE3B_H_INCLUDED */

The header might be included twice indirectly. For example, if file4b.h includes file3b.h for a type definition that isn't shown, and file1b.c needs to use both header file4b.h and file3b.h, then you have some more tricky issues to resolve. Clearly, you might revise the header list to include just file4b.h. However, you might not be aware of the internal dependencies — and the code should, ideally, continue to work.

头可以间接包含两次。例如,如果file4b。h包括file3b。h表示未显示的类型定义,以及file1b。c需要同时使用header file4b。h和file3b。然后还有一些更棘手的问题需要解决。显然,您可以修改标题列表,只包含file4b.h。但是,您可能不知道内部依赖关系——理想情况下,代码应该继续工作。

Further, it starts to get tricky because you might include file4b.h before including file3b.h to generate the definitions, but the normal header guards on file3b.h would prevent the header being reincluded.

此外,它开始变得棘手,因为您可能包含file4b。包括file3b前h。h生成定义,但在file3b上的正常头保护。h将防止头被重新包含。

So, you need to include the body of file3b.h at most once for declarations, 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).

因此,您需要包含file3b的主体。h最多一次用于声明,最多一次用于定义,但是您可能需要同时使用一个翻译单元(TU——源文件和它使用的头的组合)。

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.
  • 外部的。h表示外部宏定义,等等。
  • file1c.h to define types (notably, struct oddball, the type of oddball_struct).
  • file1c。h定义类型(特别是,struct oddball, oddball_struct的类型)。
  • file2c.h to define or declare the global variables.
  • file2c。定义或声明全局变量。
  • file3c.c which defines the global variables.
  • file3c。c定义了全局变量。
  • file4c.c which simply uses the global variables.
  • file4c。它只使用全局变量。
  • file5c.c which shows that you can declare and then define the global variables.
  • file5c。c表示可以声明并定义全局变量。
  • file6c.c which shows that you can define and then (attempt to) declare the global variables.
  • file6c。c表示您可以定义然后(尝试)声明全局变量。

In these examples, file5c.c and file6c.c directly include the header file2c.h several times, but that is the simplest way to show that the mechanism works. It means that if the header was indirectly included twice, it would also be safe.

在这些例子中,file5c。c和file6c。c直接包含头文件2c。h有好几次,但这是最简单的方法来证明这个机制是有效的。这意味着如果头被间接包含两次,那么它也是安全的。

The restrictions for this to work are:

这项工作的限制是:

  1. The header defining or declaring the global variables may not itself define any types.
  2. 定义或声明全局变量的头本身可能不定义任何类型。
  3. Immediately before you include a header that should define variables, you define the macro DEFINE_VARIABLES.
  4. 在包含应该定义变量的头之前,您需要定义宏定义变量。
  5. The header defining or declaring the variables has stylized contents.
  6. 定义或声明变量的头具有程式化的内容。

external.h

/*
** This header must not contain header guards (like <assert.h> must not).
** Each time it is invoked, it redefines the macros EXTERN, INITIALIZE
** based on whether macro DEFINE_VARIABLES is currently defined.
*/
#undef EXTERN
#undef INITIALIZE

#ifdef DEFINE_VARIABLES
#define EXTERN              /* nothing */
#define INITIALIZE(...)     = __VA_ARGS__
#else
#define EXTERN              extern
#define INITIALIZE(...)     /* nothing */
#endif /* DEFINE_VARIABLES */

file1c.h

#ifndef FILE1C_H_INCLUDED
#define FILE1C_H_INCLUDED

struct oddball
{
    int a;
    int b;
};

extern void use_them(void);
extern int increment(void);
extern int oddball_value(void);

#endif /* FILE1C_H_INCLUDED */

file2c.h

/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE2C_H_DEFINITIONS)
#undef FILE2C_H_INCLUDED
#endif

#ifndef FILE2C_H_INCLUDED
#define FILE2C_H_INCLUDED

#include "external.h"   /* Support macros EXTERN, INITIALIZE */
#include "file1c.h"     /* Type definition for struct oddball */

#if !defined(DEFINE_VARIABLES) || !defined(FILE2C_H_DEFINITIONS)

/* Global variable declarations / definitions */
EXTERN int global_variable INITIALIZE(37);
EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 });

#endif /* !DEFINE_VARIABLES || !FILE2C_H_DEFINITIONS */

/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE2C_H_DEFINITIONS
#endif /* DEFINE_VARIABLES */

#endif /* FILE2C_H_INCLUDED */

file3c.c

#define DEFINE_VARIABLES
#include "file2c.h"  /* Variables now defined and initialized */

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

file4c.c

#include "file2c.h"
#include <stdio.h>

void use_them(void)
{
    printf("Global variable: %d\n", global_variable++);
    oddball_struct.a += global_variable;
    oddball_struct.b -= global_variable / 2;
}

file5c.c

#include "file2c.h"     /* Declare variables */

#define DEFINE_VARIABLES
#include "file2c.h"  /* Variables now defined and initialized */

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

file6c.c

#define DEFINE_VARIABLES
#include "file2c.h"     /* Variables now defined and initialized */

#include "file2c.h"     /* Declare variables */

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

The next source file completes the source (provides a main program) for prog5, prog6 and prog7:

下一个源文件完成了prog5、prog6和prog7的源文件(提供一个主程序):

prog5.c

#include "file2c.h"
#include <stdio.h>

int main(void)
{
    use_them();
    global_variable += 19;
    use_them();
    printf("Increment: %d\n", increment());
    printf("Oddball:   %d\n", oddball_value());
    return 0;
}
  • prog5 uses prog5.c, file3c.c, file4c.c, file1c.h, file2c.h, external.h.
  • prog5使用prog5。c,file3c。c,file4c。c,file1c。h,file2c。h,external.h。
  • prog6 uses prog5.c, file5c.c, file4c.c, file1c.h, file2c.h, external.h.
  • prog6使用prog5。c,file5c。c,file4c。c,file1c。h,file2c。h,external.h。
  • prog7 uses prog5.c, file6c.c, file4c.c, file1c.h, file2c.h, external.h.
  • prog7使用prog5。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 by another header (say file7c.h) that defines variables. There isn't an easy way around that other than "don't do it".

这个方案避免了大多数问题。只有当定义变量的头(如file2c.h)被定义变量的另一个头(如file7c.h)包含时,才会出现问题。除了“不要做”之外,没有一个简单的方法。

You can partially work around the problem by revising file2c.h into file2d.h:

您可以通过修改file2c来部分解决这个问题。h file2d.h:

file2d.h

/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE2D_H_DEFINITIONS)
#undef FILE2D_H_INCLUDED
#endif

#ifndef FILE2D_H_INCLUDED
#define FILE2D_H_INCLUDED

#include "external.h"   /* Support macros EXTERN, INITIALIZE */
#include "file1c.h"     /* Type definition for struct oddball */

#if !defined(DEFINE_VARIABLES) || !defined(FILE2D_H_DEFINITIONS)

/* Global variable declarations / definitions */
EXTERN int global_variable INITIALIZE(37);
EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 });

#endif /* !DEFINE_VARIABLES || !FILE2D_H_DEFINITIONS */

/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE2D_H_DEFINITIONS
#undef DEFINE_VARIABLES
#endif /* DEFINE_VARIABLES */

#endif /* FILE2D_H_INCLUDED */

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:

问题变成了“标题是否应该包含#undef DEFINE_VARIABLES?”如果您从标题中省略了这一点,并使用#define和#undef包装任何定义调用:

#define DEFINE_VARIABLES
#include "file2c.h"
#undef DEFINE_VARIABLES

in the source code (so the headers never alter the value of DEFINE_VARIABLES), then you should be clean. It is just a nuisance to have to remember to write the the extra line. An alternative might be:

在源代码中(这样标题就不会改变DEFINE_VARIABLES的值),那么您应该保持整洁。不得不记着把多余的行写出来,这是件麻烦事。另一种可能是:

#define HEADER_DEFINING_VARIABLES "file2c.h"
#include "externdef.h"

externdef.h

/*
** This header must not contain header guards (like <assert.h> must not).
** Each time it is included, the macro HEADER_DEFINING_VARIABLES should
** be defined with the name (in quotes - or possibly angle brackets) of
** the header to be included that defines variables when the macro
** DEFINE_VARIABLES is defined.  See also: external.h (which uses
** DEFINE_VARIABLES and defines macros EXTERN and INITIALIZE
** appropriately).
**
** #define HEADER_DEFINING_VARIABLES "file2c.h"
** #include "externdef.h"
*/

#if defined(HEADER_DEFINING_VARIABLES)
#define DEFINE_VARIABLES
#include HEADER_DEFINING_VARIABLES
#undef DEFINE_VARIABLES
#undef HEADER_DEFINING_VARIABLES
#endif /* HEADER_DEFINING_VARIABLES */

This is getting a tad convoluted, but seems to be secure (using the file2d.h, with no #undef DEFINE_VARIABLES in the file2d.h).

这有点复杂,但似乎很安全(使用file2d)。h,在file2d.h中没有#undef DEFINE_VARIABLES。

file7c.c

/* Declare variables */
#include "file2d.h"

/* Define variables */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"

/* Declare variables - again */
#include "file2d.h"

/* Define variables - again */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

file8c.h

/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE8C_H_DEFINITIONS)
#undef FILE8C_H_INCLUDED
#endif

#ifndef FILE8C_H_INCLUDED
#define FILE8C_H_INCLUDED

#include "external.h"   /* Support macros EXTERN, INITIALIZE */
#include "file2d.h"     /* struct oddball */

#if !defined(DEFINE_VARIABLES) || !defined(FILE8C_H_DEFINITIONS)

/* Global variable declarations / definitions */
EXTERN struct oddball another INITIALIZE({ 14, 34 });

#endif /* !DEFINE_VARIABLES || !FILE8C_H_DEFINITIONS */

/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE8C_H_DEFINITIONS
#endif /* DEFINE_VARIABLES */

#endif /* FILE8C_H_INCLUDED */

file8c.c

/* Define variables */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"

/* Define variables */
#define HEADER_DEFINING_VARIABLES "file8c.h"
#include "externdef.h"

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

The next two files complete the source for prog8 and prog9:

接下来的两个文件完成了prog8和prog9的源代码:

prog8.c

#include "file2d.h"
#include <stdio.h>

int main(void)
{
    use_them();
    global_variable += 19;
    use_them();
    printf("Increment: %d\n", increment());
    printf("Oddball:   %d\n", oddball_value());
    return 0;
}

file9c.c

#include "file2d.h"
#include <stdio.h>

void use_them(void)
{
    printf("Global variable: %d\n", global_variable++);
    oddball_struct.a += global_variable;
    oddball_struct.b -= global_variable / 2;
}
  • prog8 uses prog8.c, file7c.c, file9c.c.
  • prog8使用prog8。c,file7c。c,file9c.c。
  • prog9 uses prog8.c, file8c.c, file9c.c.
  • prog9使用prog8。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.

坦白说:这里列出的“避免重复代码”方案之所以开发出来,是因为这个问题影响了我正在处理的一些代码(但我并不拥有这些代码),而且它与答案的第一部分中列出的方案存在着微妙的关系。但是,原始的方案只允许您修改两个位置,以保持变量定义和声明的同步,这对于在整个代码库中分散的执行变量声明来说是一个很大的进步(当总共有数千个文件时,这一点非常重要)。但是,在文件名为fileNc的文件中的代码。(ch)(加上外部。h和externdef.h)表示可以使其工作。显然,创建一个头生成器脚本为变量定义和声明头文件提供标准化模板并不困难。

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 and prog8.c is the name of one of the headers that are included. It would be possible to reorganize the code so that the main() function was not repeated, but it would conceal more than it revealed.)

NB这些是玩具程序,只有足够的代码使它们稍微有趣一点。这些例子中有重复的地方,可以删除,但不是为了简化教学解释。例如:prog5的区别。c和prog8。c是包含其中一个头的名称。可以重新组织代码,使main()函数不被重复,但是它隐藏的东西比它显示的要多)。

#2


106  

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.

extern变量是在另一个转换单元中定义的变量的声明(感谢sbi的纠正)。这意味着变量的存储被分配到另一个文件中。

Say you have two .c-files test1.c and test2.c. If you define a global variable int test1_var; in test1.c and you'd like to access this variable in test2.c you have to use extern int test1_var; in test2.c.

假设您有两个.c文件test1。c和test2.c。如果定义一个全局变量int test1_var;test1。你想在test2中访问这个变量。c必须使用extern int test1_var;在test2.c。

Complete sample:

完整的示例:

$ cat test1.c 
int test1_var = 5;
$ cat test2.c
#include <stdio.h>

extern int test1_var;

int main(void) {
    printf("test1_var = %d\n", test1_var);
    return 0;
}
$ gcc test1.c test2.c -o test
$ ./test
test1_var = 5

#3


33  

Extern is the keyword you use to declare that the variable itself resides in another translation unit.

Extern是用于声明变量本身驻留在另一个转换单元中的关键字。

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.

所以你可以决定在转换单元中使用一个变量然后从另一个中访问它,然后在第二个中你将它声明为extern,符号将被链接者解析。

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,你会得到两个命名相同但毫无关联的变量,以及变量多重定义的错误。

#4


22  

I like to think of an extern variable as a promise that you make to the compiler.

我喜欢把extern变量看作是对编译器的承诺。

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."

你是在说,相信我。在链接时,这个引用将是可解的。

#5


16  

extern tells the compiler to trust you that the memory for this variable is declared elsewhere, so it doesnt try to allocate/check memory.

extern告诉编译器信任您该变量的内存是在别处声明的,因此它不会尝试分配/检查内存。

Therefore, you can compile a file that has reference to an extern, but you can not link if that memory is not declared somewhere.

因此,您可以编译一个引用了一个extern的文件,但是如果某个地方没有声明该内存,您将无法链接该内存。

Useful for global variables and libraries, but dangerous because the linker does not type check.

对全局变量和库有用,但是危险,因为链接器不进行类型检查。

#6


14  

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将变量定义转变为变量声明。看看这个线程,声明和定义的区别是什么。

#7


10  

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.

对extern的正确解释是你告诉编译器一些东西。您告诉编译器,尽管现在还没有出现,但是声明的变量将以某种方式被链接器找到(通常是在另一个对象(文件)中)。链接器将会是幸运的人找到所有的东西并把它放在一起,不管你是否有外部声明。

#8


7  

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

在C中,一个变量在一个文件中。c被赋予局部范围。编译器期望在同一个文件示例中包含变量的定义。当它不相同的时候,它会抛出一个错误。另一方面,函数是默认全局作用域。因此,您不必向编译器显式地提到“看,伙计……”你可以在这里找到这个函数的定义。对于包含其声明的文件的函数来说,就足够了。(实际称为头文件的文件)。例如,考虑以下两个文件:example.c

#include<stdio.h>
extern int a;
main(){
       printf("The value of a is <%d>\n",a);
}

example1.c

example1.c

int a = 5;

Now when you compile the two files together, using the following commands :

现在,当您使用以下命令编译这两个文件时:

step 1)cc -o ex example.c example1.c step 2)./ex

步骤1)cc -o示例。c例二。c步骤2)。/交货

You get the following output : The value of a is <5>

得到以下输出:a的值为<5>。

#9


6  

extern keyword is used with the variable for its identification as a global variable.

extern关键字用于变量作为全局变量的标识时。

It also represents that you can use the variable declared using extern keyword in any file though it is declared/defined in other file.

它还表示可以在任何文件中使用外部关键字声明的变量,尽管在其他文件中声明/定义了该变量。

#10


3  

but how does that work precisely?

但这究竟是如何运作的呢?

Let's see how GCC 4.8 ELF implements it

让我们看看GCC 4.8 ELF是如何实现它的

main.c:

c:

#include <stdio.h>

int not_extern_int = 1;
extern int extern_int;

void main() {
    printf("%d\n", not_extern_int);
    printf("%d\n", extern_int);
}

Compile and decompile:

编译和反编译:

gcc -c main.c
readelf -s main.o

Output contains:

输出包含:

Num:    Value          Size Type    Bind   Vis      Ndx Name
 9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 not_extern_int
12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND extern_int

The System V ABI Update ELF spec "Symbol Table" chapter explains:

System V ABI更新ELF spec“Symbol Table”一章解释说:

SHN_UNDEF This section table index means the symbol is undefined. When the link editor combines this object file with another that defines the indicated symbol, this file's references to the symbol will be linked to the actual definition.

此节表索引表示符号未定义。当链接编辑器将该对象文件与另一个定义了所指示符号的文件组合在一起时,该文件对该符号的引用将链接到实际的定义。

which is basically the behavior the C standard gives to extern variables.

这基本上就是C标准给出的外部变量的行为。

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.

从现在开始,链接器的工作就是生成最终的程序,但是外部信息已经从源代码中提取到对象文件中。

#11


3  

First off, the extern keyword is not used for defining a variable; rather it is used for declaring a variable. I can say extern is a storage class, not a data type.

首先,extern关键字未用于定义变量;而是用于声明变量。可以说extern是一个存储类,而非数据类型。

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用于让其他C文件或外部组件知道已经在某处定义了该变量。示例:如果正在构建一个库,则不需要在库本身的某个地方强制定义全局变量。库将被直接编译,但是在链接文件时,它会检查定义。

#12


2  

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.

如果不希望程序访问变量或函数,可以使用static,它告诉编译器这个变量或函数不能在这个模块之外使用。

#13


2  

extern is used so one first.c file can have full access to a global parameter in another second.c file.

extern是首先使用的。c文件可以在另一秒内完全访问全局参数。c文件。

The extern can be declared in the first.c file or in any of the header files first.c includes.

外部可以在第一个中声明。c文件或任何头文件中首先。c包括。

#14


0  

extern simply means a variable is defined elsewhere (e.g., in another file).

外部简单地表示在其他地方定义了一个变量(例如,在另一个文件中)。

#15


-1  

I tried to show all scenarios (for functions and variables) in the following picture:

我试着在下图中显示所有的场景(函数和变量):

如何使用extern来共享源文件之间的变量?

#1


1467  

Using extern is only of relevance when the program you're building consists of multiple source files linked together, where some of the variables defined, for example, in source file file1.c need to be referenced in other source files, such as file2.c.

当您正在构建的程序由多个链接在一起的源文件组成时,使用extern只具有相关性,例如,源代码文件1中定义了一些变量。c需要在其他源文件中引用,例如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.

头由定义变量的源文件和引用变量的所有源文件包含。对于每个程序,一个源文件(只有一个源文件)定义变量。类似地,一个头文件(只有一个头文件)应该声明变量。头文件至关重要;它允许在独立的TUs(翻译单位-考虑源文件)之间进行交叉检查,并确保一致性。

Although there are other ways of doing it, this method is simple and reliable. It is demonstrated by file3.h, file1.c and file2.c:

虽然有其他的方法,但这个方法简单可靠。它由file3演示。h,file1。c和file2.c:

file3.h

extern int global_variable;  /* Declaration of the variable */

file1.c

#include "file3.h"  /* Declaration made available here */
#include "prog1.h"  /* Function declarations */

/* Variable defined here */
int global_variable = 37;    /* Definition checked against declaration */

int increment(void) { return global_variable++; }

file2.c

#include "file3.h"
#include "prog1.h"
#include <stdio.h>

void use_it(void)
{
    printf("Global variable: %d\n", global_variable++);
}

That's the best way to declare and define global variables.

这是声明和定义全局变量的最佳方式。


The next two files complete the source for prog1:

接下来的两个文件完成了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 headers for consistency — to match the extern in front of variable declarations in headers. Many people prefer not to use extern in front of function declarations; the compiler doesn't care — and ultimately, neither do I as long as you're consistent, at least within a source file.

完整的程序显示了使用函数,因此函数声明已经进入。C99和C11都要求在使用函数之前声明或定义函数(而C90没有声明函数,这是有充分理由的)。为了保持一致性,我在header中的函数声明前使用了关键字extern——为了匹配header中变量声明前的extern。许多人不喜欢在函数声明前使用extern;编译器不关心——而且最终,只要您是一致的,我也不关心,至少在一个源文件中。

prog1.h

extern void use_it(void);
extern int increment(void);

prog1.c

#include "file3.h"
#include "prog1.h"
#include <stdio.h>

int main(void)
{
    use_it();
    global_variable += 19;
    use_it();
    printf("Increment: %d\n", increment());
    return 0;
}
  • prog1 uses prog1.c, file1.c, file2.c, file3.h and prog1.h.
  • prog1使用prog1。c,file1。c、file2。c,file3。h和prog1.h。

The file prog1.mk is a makefile for prog1 only. It will work with most versions of make produced since about the turn of the millennium. It is not tied specifically to GNU Make.

文件prog1。mk是只用于prog1的makefile。从世纪之交开始,它将与大多数版本的产品合作。它不是专门与GNU Make绑定的。

prog1.mk

# Minimal makefile for prog1

PROGRAM = prog1
FILES.c = prog1.c file1.c file2.c
FILES.h = prog1.h file3.h
FILES.o = ${FILES.c:.c=.o}

CC      = gcc
SFLAGS  = -std=c11
GFLAGS  = -g
OFLAGS  = -O3
WFLAG1  = -Wall
WFLAG2  = -Wextra
WFLAG3  = -Werror
WFLAG4  = -Wstrict-prototypes
WFLAG5  = -Wmissing-prototypes
WFLAGS  = ${WFLAG1} ${WFLAG2} ${WFLAG3} ${WFLAG4} ${WFLAG5}
UFLAGS  = # Set on command line only

CFLAGS  = ${SFLAGS} ${GFLAGS} ${OFLAGS} ${WFLAGS} ${UFLAGS}
LDFLAGS =
LDLIBS  =

all:    ${PROGRAM}

${PROGRAM}: ${FILES.o}
    ${CC} -o $@ ${CFLAGS} ${FILES.o} ${LDFLAGS} ${LDLIBS}

prog1.o: ${FILES.h}
file1.o: ${FILES.h}
file2.o: ${FILES.h}

# If it exists, prog1.dSYM is a directory on macOS
DEBRIS = a.out core *~ *.dSYM
RM_FR  = rm -fr

clean:
    ${RM_FR} ${FILES.o} ${PROGRAM} ${DEBRIS}

Guidelines

Rules to be broken by experts only, and only with good reason:

只有专家才能打破这些规则,而且必须有充分的理由:

  • A header file only contains extern declarations of variables — never static or unqualified variable definitions.
  • 头文件只包含变量的extern声明—从不静态或非限定变量定义。
  • 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.

这个答案的源代码和文本可以在src/so-0143-3204子目录中GitHub上的SOQ(堆栈溢出问题)存储库中找到。

If you're not an experienced C programmer, you could (and perhaps should) stop reading here.

如果你不是一个有经验的C程序员,你可以(也许应该)停止在这里阅读。

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:

有了一些(实际上是许多)C编译器,您也可以不受所谓的变量“通用”定义的影响。这里的“Common”指的是Fortran语言中使用的一种技术,它使用(可能命名的)公共块在源文件之间共享变量。这里发生的是,许多文件中的每一个都提供了变量的暂定定义。只要只有一个文件提供了初始化的定义,那么不同的文件最终会共享一个通用的单一变量定义:

file10.c

#include "prog2.h"

int i;   /* Do not do this in portable code */

void inc(void) { i++; }

file11.c

#include "prog2.h"

int i;   /* Do not do this in portable code */

void dec(void) { i--; }

file12.c

#include "prog2.h"
#include <stdio.h>

int i = 9;   /* Do not do this in portable code */

void put(void) { printf("i = %d\n", i); }

This technique does not conform to the letter of the C standard and the 'one definition rule', but the C standard lists it as a common variation on its one definition rule.

这种技术不符合C标准的字母和“一个定义规则”,但是C标准将它列为其一个定义规则的常见变体。

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 declared i as a double instead of as an int, C's type-unsafe linkers probably would not spot the mismatch. If you're on a machine with 64-bit int and double, you'd not even get a warning; on a machine with 32-bit int and 64-bit double, 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.

由于不总是支持这种技术,所以最好避免使用它,尤其是当代码需要可移植时。使用这种技术,您还可以使用无意类型punning。如果其中一个文件将i声明为double,而不是int,那么C的不安全类型链接器可能不会发现不匹配。如果您在一台具有64位int和双精度的机器上,您甚至不会得到警告;在一台32位int和64位double的机器上,您可能会得到关于不同大小的警告——链接器将使用最大的大小,就像Fortran程序将使用任何公共块中最大的大小一样。

This is mentioned in the C standard in informative Annex J as a common extension:

在附录J中,C标准中提到了这一点:

J.5.11 Multiple external definitions

J.5.11多个外部定义

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).

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


The next two files complete the source for prog2:

接下来的两个文件完成了prog2的源代码:

prog2.h

extern void dec(void);
extern void put(void);
extern void inc(void);

prog2.c

#include "prog2.h"
#include <stdio.h>

int main(void)
{
    inc();
    put();
    dec();
    put();
    dec();
    put();
}
  • prog2 uses prog2.c, file10.c, file11.c, file12.c, prog2.h.
  • prog2使用prog2。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, 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.

正如这里的注释中所指出的,正如我对类似问题的回答中所述,对全局变量使用多个定义会导致未定义的行为,这是“任何事情都可能发生”的标准说法。可能发生的一件事是程序按照你的期望运行;而J.5.11说,“你可能比你应得的更幸运”。但是依赖于一个外部变量的多个定义的程序——带有或不带有显式的“extern”关键字——并不是一个严格遵循的程序,并且不能保证在任何地方都能正常运行。等价地说:它包含一个可能显示自己也可能不显示自己的错误。

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

int some_var;    /* Do not do this in a header!!! */

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.

注意1:如果header定义了变量,但没有使用extern关键字,那么包含header的每个文件都创建了变量的临时定义。如前所述,这通常会起作用,但是C标准并不能保证它会起作用。

broken_header.h

int some_var = 13;    /* Only one source file in a program can use this */

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.

注意2:如果头文件定义并初始化变量,那么给定程序中只有一个源文件可以使用头文件。由于头文件主要用于共享信息,所以创建一个只能使用一次的头文件有点愚蠢。

seldom_correct.h

static int hidden_global = 3;   /* Each source file gets its own copy  */

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.

注意3:如果标头定义了一个静态变量(有或没有初始化),那么每个源文件最后都有自己的“全局”变量的私有版本。

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 is included in every file that uses it — including the one that defines it. This ensures that everything is self-consistent.

使用我首先展示的头部技术。它在任何地方都可靠地工作。特别要注意的是,声明global_variable的头包含在每个使用它的文件中——包括定义它的文件。这确保了一切都是自洽的。

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.

如果你不是一个有经验的C程序员,你应该停止阅读这里。


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.

另一个问题可能是需要在许多“主程序”中定义变量。这通常是虚假的担忧;您可以简单地引入一个C源文件来定义变量,并将每个程序生成的对象文件链接起来。

A typical scheme works like this, using the original global variable illustrated in file3.h:

典型的方案是这样工作的,使用file3.h中所示的原始全局变量:

file3a.h

#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#else
#define EXTERN extern
#endif /* DEFINE_VARIABLES */

EXTERN int global_variable;

file1a.c

#define DEFINE_VARIABLES
#include "file3a.h"  /* Variable defined - but not initialized */
#include "prog3.h"

int increment(void) { return global_variable++; }

file2a.c

#include "file3a.h"
#include "prog3.h"
#include <stdio.h>

void use_it(void)
{
    printf("Global variable: %d\n", global_variable++);
}

The next two files complete the source for prog3:

接下来的两个文件完成了prog3的源代码:

prog3.h

extern void use_it(void);
extern int increment(void);

prog3.c

#include "file3a.h"
#include "prog3.h"
#include <stdio.h>

int main(void)
{
    use_it();
    global_variable += 19;
    use_it();
    printf("Increment: %d\n", increment());
    return 0;
}
  • prog3 uses prog3.c, file1a.c, file2a.c, file3a.h, prog3.h.
  • prog3使用prog3。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.)

这个方案的问题在于它没有提供全局变量的初始化。使用C99或C11以及宏的变量参数列表,您可以定义一个支持初始化的宏。(C89不支持宏中的变量参数列表,因此无法轻松处理任意长的初始化。)

file3b.h

#ifdef DEFINE_VARIABLES
#define EXTERN                  /* nothing */
#define INITIALIZER(...)        = __VA_ARGS__
#else
#define EXTERN                  extern
#define INITIALIZER(...)        /* nothing */
#endif /* DEFINE_VARIABLES */

EXTERN int global_variable INITIALIZER(37);
EXTERN struct { int a; int b; } oddball_struct INITIALIZER({ 41, 43 });

Reverse contents of #if and #else blocks, fixing bug identified by Denis Kniazhev

#if和#else块的反向内容,由Denis Kniazhev确定的修复bug。

file1b.c

#define DEFINE_VARIABLES
#include "file3b.h"  /* Variables now defined and initialized */
#include "prog4.h"

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

file2b.c

#include "file3b.h"
#include "prog4.h"
#include <stdio.h>

void use_them(void)
{
    printf("Global variable: %d\n", global_variable++);
    oddball_struct.a += global_variable;
    oddball_struct.b -= global_variable / 2;
}

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 support for variable argument lists for macros, initializers that need to contain commas are very problematic.

显然,古怪结构的代码不是您通常编写的,但它说明了问题所在。第二次初始化器调用的第一个参数是{41,其余的参数(本例中的单数)是43}。如果没有C99或类似的对宏的变量参数列表的支持,需要包含逗号的初始化器就会很成问题。

Correct header file3b.h included (instead of fileba.h) per Denis Kniazhev

正确的标题file3b。h包括(而不是fileba.h)每个Denis Kniazhev。


The next two files complete the source for prog4:

接下来的两个文件完成了prog4的源代码:

prog4.h

extern int increment(void);
extern int oddball_value(void);
extern void use_them(void);

prog4.c

#include "file3b.h"
#include "prog4.h"
#include <stdio.h>

int main(void)
{
    use_them();
    global_variable += 19;
    use_them();
    printf("Increment: %d\n", increment());
    printf("Oddball:   %d\n", oddball_value());
    return 0;
}
  • prog4 uses prog4.c, file1b.c, file2b.c, prog4.h, file3b.h.
  • prog4使用prog4。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:

任何标头都应该受到保护,以防止重新包含,以便类型定义(枚举、结构或联合类型,或通常的typedef)不会导致问题。标准的技术是将头部的主体包裹在头部防护中,例如:

#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED

...contents of header...

#endif /* FILE3B_H_INCLUDED */

The header might be included twice indirectly. For example, if file4b.h includes file3b.h for a type definition that isn't shown, and file1b.c needs to use both header file4b.h and file3b.h, then you have some more tricky issues to resolve. Clearly, you might revise the header list to include just file4b.h. However, you might not be aware of the internal dependencies — and the code should, ideally, continue to work.

头可以间接包含两次。例如,如果file4b。h包括file3b。h表示未显示的类型定义,以及file1b。c需要同时使用header file4b。h和file3b。然后还有一些更棘手的问题需要解决。显然,您可以修改标题列表,只包含file4b.h。但是,您可能不知道内部依赖关系——理想情况下,代码应该继续工作。

Further, it starts to get tricky because you might include file4b.h before including file3b.h to generate the definitions, but the normal header guards on file3b.h would prevent the header being reincluded.

此外,它开始变得棘手,因为您可能包含file4b。包括file3b前h。h生成定义,但在file3b上的正常头保护。h将防止头被重新包含。

So, you need to include the body of file3b.h at most once for declarations, 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).

因此,您需要包含file3b的主体。h最多一次用于声明,最多一次用于定义,但是您可能需要同时使用一个翻译单元(TU——源文件和它使用的头的组合)。

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.
  • 外部的。h表示外部宏定义,等等。
  • file1c.h to define types (notably, struct oddball, the type of oddball_struct).
  • file1c。h定义类型(特别是,struct oddball, oddball_struct的类型)。
  • file2c.h to define or declare the global variables.
  • file2c。定义或声明全局变量。
  • file3c.c which defines the global variables.
  • file3c。c定义了全局变量。
  • file4c.c which simply uses the global variables.
  • file4c。它只使用全局变量。
  • file5c.c which shows that you can declare and then define the global variables.
  • file5c。c表示可以声明并定义全局变量。
  • file6c.c which shows that you can define and then (attempt to) declare the global variables.
  • file6c。c表示您可以定义然后(尝试)声明全局变量。

In these examples, file5c.c and file6c.c directly include the header file2c.h several times, but that is the simplest way to show that the mechanism works. It means that if the header was indirectly included twice, it would also be safe.

在这些例子中,file5c。c和file6c。c直接包含头文件2c。h有好几次,但这是最简单的方法来证明这个机制是有效的。这意味着如果头被间接包含两次,那么它也是安全的。

The restrictions for this to work are:

这项工作的限制是:

  1. The header defining or declaring the global variables may not itself define any types.
  2. 定义或声明全局变量的头本身可能不定义任何类型。
  3. Immediately before you include a header that should define variables, you define the macro DEFINE_VARIABLES.
  4. 在包含应该定义变量的头之前,您需要定义宏定义变量。
  5. The header defining or declaring the variables has stylized contents.
  6. 定义或声明变量的头具有程式化的内容。

external.h

/*
** This header must not contain header guards (like <assert.h> must not).
** Each time it is invoked, it redefines the macros EXTERN, INITIALIZE
** based on whether macro DEFINE_VARIABLES is currently defined.
*/
#undef EXTERN
#undef INITIALIZE

#ifdef DEFINE_VARIABLES
#define EXTERN              /* nothing */
#define INITIALIZE(...)     = __VA_ARGS__
#else
#define EXTERN              extern
#define INITIALIZE(...)     /* nothing */
#endif /* DEFINE_VARIABLES */

file1c.h

#ifndef FILE1C_H_INCLUDED
#define FILE1C_H_INCLUDED

struct oddball
{
    int a;
    int b;
};

extern void use_them(void);
extern int increment(void);
extern int oddball_value(void);

#endif /* FILE1C_H_INCLUDED */

file2c.h

/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE2C_H_DEFINITIONS)
#undef FILE2C_H_INCLUDED
#endif

#ifndef FILE2C_H_INCLUDED
#define FILE2C_H_INCLUDED

#include "external.h"   /* Support macros EXTERN, INITIALIZE */
#include "file1c.h"     /* Type definition for struct oddball */

#if !defined(DEFINE_VARIABLES) || !defined(FILE2C_H_DEFINITIONS)

/* Global variable declarations / definitions */
EXTERN int global_variable INITIALIZE(37);
EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 });

#endif /* !DEFINE_VARIABLES || !FILE2C_H_DEFINITIONS */

/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE2C_H_DEFINITIONS
#endif /* DEFINE_VARIABLES */

#endif /* FILE2C_H_INCLUDED */

file3c.c

#define DEFINE_VARIABLES
#include "file2c.h"  /* Variables now defined and initialized */

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

file4c.c

#include "file2c.h"
#include <stdio.h>

void use_them(void)
{
    printf("Global variable: %d\n", global_variable++);
    oddball_struct.a += global_variable;
    oddball_struct.b -= global_variable / 2;
}

file5c.c

#include "file2c.h"     /* Declare variables */

#define DEFINE_VARIABLES
#include "file2c.h"  /* Variables now defined and initialized */

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

file6c.c

#define DEFINE_VARIABLES
#include "file2c.h"     /* Variables now defined and initialized */

#include "file2c.h"     /* Declare variables */

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

The next source file completes the source (provides a main program) for prog5, prog6 and prog7:

下一个源文件完成了prog5、prog6和prog7的源文件(提供一个主程序):

prog5.c

#include "file2c.h"
#include <stdio.h>

int main(void)
{
    use_them();
    global_variable += 19;
    use_them();
    printf("Increment: %d\n", increment());
    printf("Oddball:   %d\n", oddball_value());
    return 0;
}
  • prog5 uses prog5.c, file3c.c, file4c.c, file1c.h, file2c.h, external.h.
  • prog5使用prog5。c,file3c。c,file4c。c,file1c。h,file2c。h,external.h。
  • prog6 uses prog5.c, file5c.c, file4c.c, file1c.h, file2c.h, external.h.
  • prog6使用prog5。c,file5c。c,file4c。c,file1c。h,file2c。h,external.h。
  • prog7 uses prog5.c, file6c.c, file4c.c, file1c.h, file2c.h, external.h.
  • prog7使用prog5。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 by another header (say file7c.h) that defines variables. There isn't an easy way around that other than "don't do it".

这个方案避免了大多数问题。只有当定义变量的头(如file2c.h)被定义变量的另一个头(如file7c.h)包含时,才会出现问题。除了“不要做”之外,没有一个简单的方法。

You can partially work around the problem by revising file2c.h into file2d.h:

您可以通过修改file2c来部分解决这个问题。h file2d.h:

file2d.h

/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE2D_H_DEFINITIONS)
#undef FILE2D_H_INCLUDED
#endif

#ifndef FILE2D_H_INCLUDED
#define FILE2D_H_INCLUDED

#include "external.h"   /* Support macros EXTERN, INITIALIZE */
#include "file1c.h"     /* Type definition for struct oddball */

#if !defined(DEFINE_VARIABLES) || !defined(FILE2D_H_DEFINITIONS)

/* Global variable declarations / definitions */
EXTERN int global_variable INITIALIZE(37);
EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 });

#endif /* !DEFINE_VARIABLES || !FILE2D_H_DEFINITIONS */

/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE2D_H_DEFINITIONS
#undef DEFINE_VARIABLES
#endif /* DEFINE_VARIABLES */

#endif /* FILE2D_H_INCLUDED */

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:

问题变成了“标题是否应该包含#undef DEFINE_VARIABLES?”如果您从标题中省略了这一点,并使用#define和#undef包装任何定义调用:

#define DEFINE_VARIABLES
#include "file2c.h"
#undef DEFINE_VARIABLES

in the source code (so the headers never alter the value of DEFINE_VARIABLES), then you should be clean. It is just a nuisance to have to remember to write the the extra line. An alternative might be:

在源代码中(这样标题就不会改变DEFINE_VARIABLES的值),那么您应该保持整洁。不得不记着把多余的行写出来,这是件麻烦事。另一种可能是:

#define HEADER_DEFINING_VARIABLES "file2c.h"
#include "externdef.h"

externdef.h

/*
** This header must not contain header guards (like <assert.h> must not).
** Each time it is included, the macro HEADER_DEFINING_VARIABLES should
** be defined with the name (in quotes - or possibly angle brackets) of
** the header to be included that defines variables when the macro
** DEFINE_VARIABLES is defined.  See also: external.h (which uses
** DEFINE_VARIABLES and defines macros EXTERN and INITIALIZE
** appropriately).
**
** #define HEADER_DEFINING_VARIABLES "file2c.h"
** #include "externdef.h"
*/

#if defined(HEADER_DEFINING_VARIABLES)
#define DEFINE_VARIABLES
#include HEADER_DEFINING_VARIABLES
#undef DEFINE_VARIABLES
#undef HEADER_DEFINING_VARIABLES
#endif /* HEADER_DEFINING_VARIABLES */

This is getting a tad convoluted, but seems to be secure (using the file2d.h, with no #undef DEFINE_VARIABLES in the file2d.h).

这有点复杂,但似乎很安全(使用file2d)。h,在file2d.h中没有#undef DEFINE_VARIABLES。

file7c.c

/* Declare variables */
#include "file2d.h"

/* Define variables */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"

/* Declare variables - again */
#include "file2d.h"

/* Define variables - again */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

file8c.h

/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE8C_H_DEFINITIONS)
#undef FILE8C_H_INCLUDED
#endif

#ifndef FILE8C_H_INCLUDED
#define FILE8C_H_INCLUDED

#include "external.h"   /* Support macros EXTERN, INITIALIZE */
#include "file2d.h"     /* struct oddball */

#if !defined(DEFINE_VARIABLES) || !defined(FILE8C_H_DEFINITIONS)

/* Global variable declarations / definitions */
EXTERN struct oddball another INITIALIZE({ 14, 34 });

#endif /* !DEFINE_VARIABLES || !FILE8C_H_DEFINITIONS */

/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE8C_H_DEFINITIONS
#endif /* DEFINE_VARIABLES */

#endif /* FILE8C_H_INCLUDED */

file8c.c

/* Define variables */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"

/* Define variables */
#define HEADER_DEFINING_VARIABLES "file8c.h"
#include "externdef.h"

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

The next two files complete the source for prog8 and prog9:

接下来的两个文件完成了prog8和prog9的源代码:

prog8.c

#include "file2d.h"
#include <stdio.h>

int main(void)
{
    use_them();
    global_variable += 19;
    use_them();
    printf("Increment: %d\n", increment());
    printf("Oddball:   %d\n", oddball_value());
    return 0;
}

file9c.c

#include "file2d.h"
#include <stdio.h>

void use_them(void)
{
    printf("Global variable: %d\n", global_variable++);
    oddball_struct.a += global_variable;
    oddball_struct.b -= global_variable / 2;
}
  • prog8 uses prog8.c, file7c.c, file9c.c.
  • prog8使用prog8。c,file7c。c,file9c.c。
  • prog9 uses prog8.c, file8c.c, file9c.c.
  • prog9使用prog8。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.

坦白说:这里列出的“避免重复代码”方案之所以开发出来,是因为这个问题影响了我正在处理的一些代码(但我并不拥有这些代码),而且它与答案的第一部分中列出的方案存在着微妙的关系。但是,原始的方案只允许您修改两个位置,以保持变量定义和声明的同步,这对于在整个代码库中分散的执行变量声明来说是一个很大的进步(当总共有数千个文件时,这一点非常重要)。但是,在文件名为fileNc的文件中的代码。(ch)(加上外部。h和externdef.h)表示可以使其工作。显然,创建一个头生成器脚本为变量定义和声明头文件提供标准化模板并不困难。

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 and prog8.c is the name of one of the headers that are included. It would be possible to reorganize the code so that the main() function was not repeated, but it would conceal more than it revealed.)

NB这些是玩具程序,只有足够的代码使它们稍微有趣一点。这些例子中有重复的地方,可以删除,但不是为了简化教学解释。例如:prog5的区别。c和prog8。c是包含其中一个头的名称。可以重新组织代码,使main()函数不被重复,但是它隐藏的东西比它显示的要多)。

#2


106  

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.

extern变量是在另一个转换单元中定义的变量的声明(感谢sbi的纠正)。这意味着变量的存储被分配到另一个文件中。

Say you have two .c-files test1.c and test2.c. If you define a global variable int test1_var; in test1.c and you'd like to access this variable in test2.c you have to use extern int test1_var; in test2.c.

假设您有两个.c文件test1。c和test2.c。如果定义一个全局变量int test1_var;test1。你想在test2中访问这个变量。c必须使用extern int test1_var;在test2.c。

Complete sample:

完整的示例:

$ cat test1.c 
int test1_var = 5;
$ cat test2.c
#include <stdio.h>

extern int test1_var;

int main(void) {
    printf("test1_var = %d\n", test1_var);
    return 0;
}
$ gcc test1.c test2.c -o test
$ ./test
test1_var = 5

#3


33  

Extern is the keyword you use to declare that the variable itself resides in another translation unit.

Extern是用于声明变量本身驻留在另一个转换单元中的关键字。

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.

所以你可以决定在转换单元中使用一个变量然后从另一个中访问它,然后在第二个中你将它声明为extern,符号将被链接者解析。

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,你会得到两个命名相同但毫无关联的变量,以及变量多重定义的错误。

#4


22  

I like to think of an extern variable as a promise that you make to the compiler.

我喜欢把extern变量看作是对编译器的承诺。

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."

你是在说,相信我。在链接时,这个引用将是可解的。

#5


16  

extern tells the compiler to trust you that the memory for this variable is declared elsewhere, so it doesnt try to allocate/check memory.

extern告诉编译器信任您该变量的内存是在别处声明的,因此它不会尝试分配/检查内存。

Therefore, you can compile a file that has reference to an extern, but you can not link if that memory is not declared somewhere.

因此,您可以编译一个引用了一个extern的文件,但是如果某个地方没有声明该内存,您将无法链接该内存。

Useful for global variables and libraries, but dangerous because the linker does not type check.

对全局变量和库有用,但是危险,因为链接器不进行类型检查。

#6


14  

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将变量定义转变为变量声明。看看这个线程,声明和定义的区别是什么。

#7


10  

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.

对extern的正确解释是你告诉编译器一些东西。您告诉编译器,尽管现在还没有出现,但是声明的变量将以某种方式被链接器找到(通常是在另一个对象(文件)中)。链接器将会是幸运的人找到所有的东西并把它放在一起,不管你是否有外部声明。

#8


7  

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

在C中,一个变量在一个文件中。c被赋予局部范围。编译器期望在同一个文件示例中包含变量的定义。当它不相同的时候,它会抛出一个错误。另一方面,函数是默认全局作用域。因此,您不必向编译器显式地提到“看,伙计……”你可以在这里找到这个函数的定义。对于包含其声明的文件的函数来说,就足够了。(实际称为头文件的文件)。例如,考虑以下两个文件:example.c

#include<stdio.h>
extern int a;
main(){
       printf("The value of a is <%d>\n",a);
}

example1.c

example1.c

int a = 5;

Now when you compile the two files together, using the following commands :

现在,当您使用以下命令编译这两个文件时:

step 1)cc -o ex example.c example1.c step 2)./ex

步骤1)cc -o示例。c例二。c步骤2)。/交货

You get the following output : The value of a is <5>

得到以下输出:a的值为<5>。

#9


6  

extern keyword is used with the variable for its identification as a global variable.

extern关键字用于变量作为全局变量的标识时。

It also represents that you can use the variable declared using extern keyword in any file though it is declared/defined in other file.

它还表示可以在任何文件中使用外部关键字声明的变量,尽管在其他文件中声明/定义了该变量。

#10


3  

but how does that work precisely?

但这究竟是如何运作的呢?

Let's see how GCC 4.8 ELF implements it

让我们看看GCC 4.8 ELF是如何实现它的

main.c:

c:

#include <stdio.h>

int not_extern_int = 1;
extern int extern_int;

void main() {
    printf("%d\n", not_extern_int);
    printf("%d\n", extern_int);
}

Compile and decompile:

编译和反编译:

gcc -c main.c
readelf -s main.o

Output contains:

输出包含:

Num:    Value          Size Type    Bind   Vis      Ndx Name
 9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 not_extern_int
12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND extern_int

The System V ABI Update ELF spec "Symbol Table" chapter explains:

System V ABI更新ELF spec“Symbol Table”一章解释说:

SHN_UNDEF This section table index means the symbol is undefined. When the link editor combines this object file with another that defines the indicated symbol, this file's references to the symbol will be linked to the actual definition.

此节表索引表示符号未定义。当链接编辑器将该对象文件与另一个定义了所指示符号的文件组合在一起时,该文件对该符号的引用将链接到实际的定义。

which is basically the behavior the C standard gives to extern variables.

这基本上就是C标准给出的外部变量的行为。

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.

从现在开始,链接器的工作就是生成最终的程序,但是外部信息已经从源代码中提取到对象文件中。

#11


3  

First off, the extern keyword is not used for defining a variable; rather it is used for declaring a variable. I can say extern is a storage class, not a data type.

首先,extern关键字未用于定义变量;而是用于声明变量。可以说extern是一个存储类,而非数据类型。

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用于让其他C文件或外部组件知道已经在某处定义了该变量。示例:如果正在构建一个库,则不需要在库本身的某个地方强制定义全局变量。库将被直接编译,但是在链接文件时,它会检查定义。

#12


2  

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.

如果不希望程序访问变量或函数,可以使用static,它告诉编译器这个变量或函数不能在这个模块之外使用。

#13


2  

extern is used so one first.c file can have full access to a global parameter in another second.c file.

extern是首先使用的。c文件可以在另一秒内完全访问全局参数。c文件。

The extern can be declared in the first.c file or in any of the header files first.c includes.

外部可以在第一个中声明。c文件或任何头文件中首先。c包括。

#14


0  

extern simply means a variable is defined elsewhere (e.g., in another file).

外部简单地表示在其他地方定义了一个变量(例如,在另一个文件中)。

#15


-1  

I tried to show all scenarios (for functions and variables) in the following picture:

我试着在下图中显示所有的场景(函数和变量):

如何使用extern来共享源文件之间的变量?