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

我知道C中的全局变量有时有extern关键字。什么是extern变量?声明是什么样子的?它的作用域是什么?

这与跨源文件共享变量有关,但它是如何精确工作的?我在哪里使用extern

894759 次浏览

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

因此,您可以决定在翻译单元中使用变量,然后从另一个单元访问它,然后在第二个单元中将其声明为extern,并且符号将由链接器解析。

如果你不将其声明为extern,你会得到两个命名相同但根本不相关的变量,以及一个变量的多个定义的错误。

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

假设您有两个.c文件test1.ctest2.c。如果您在test1.c中定义了一个全局变量int test1_var;并且您想在test2.c中访问此变量,则必须在test2.c中使用extern int test1_var;

完整样本:

$ cat test1.cint 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$ ./testtest1_var = 5

添加extern会将变量定义变成变量声明。关于声明和定义之间的区别,请参阅这个线程

extern告诉编译器信任您,此变量的内存已在其他地方声明,因此它不会尝试分配/检查内存。

因此,您可以编译引用extern的文件,但如果该内存未在某处声明,则无法链接。

对全局变量和库很有用,但很危险,因为链接器不键入check。

使用extern仅在您正在构建的程序时才有意义包含多个链接在一起的源文件,其中一些定义的变量,例如,在源文件file1.c中需要在其他源文件中引用,例如file2.c

重要的是要理解定义 a之间的区别变量和宣布 a变量

  • 当编译器被告知一个变量是宣布时变量存在(这是它的类型);它不分配在这一点上存储变量。

  • 当编译器将存储分配给变量。

您可以多次声明一个变量(尽管一次就足够了);您只能在给定范围内定义一次。变量定义也是一个声明,但不是所有的变量声明是定义。

声明和定义全局变量的最佳方法

声明和定义全局变量的干净、可靠的方法是使用包含变量extern声明的头文件。

标头包含在定义变量的一个源文件中以及引用该变量的所有源文件。对于每个程序,一个源文件(并且只有一个源文件)定义了变量。类似地,一个头文件(并且只有一个头文件)应该声明变量。头文件至关重要;它可以在独立的TU(翻译单位-考虑源文件)并确保一致性。

虽然还有其他方法,但这种方法很简单可靠。它由file3.hfile1.cfile2.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++);}

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


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

所示的完整程序使用函数,因此函数声明具有爬进去。C99和C11都需要在它们之前声明或定义函数使用(而C90没有,有充分的理由)。我在标题中的函数声明前面使用关键字extern为了一致性-匹配变量前面的extern标题中的声明。许多人不喜欢在函数前面使用extern声明;编译器不在乎-最终,我也不在乎只要你是一致的,至少在源文件中。

程序1. h

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

程序1. 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使用prog1.cfile1.cfile2.cfile3.hprog1.h

文件prog1.mk仅是prog1的makefile。它将与大多数版本的make工作,因为大约轮的千年。它不是专门与GNU Make绑定的。

prog1.mk

# Minimal makefile for prog1
PROGRAM = prog1FILES.c = prog1.c file1.c file2.cFILES.h = prog1.h file3.hFILES.o = ${FILES.c:.c=.o}
CC      = gccSFLAGS  = -std=c11GFLAGS  = -gOFLAGS  = -O3WFLAG1  = -WallWFLAG2  = -WextraWFLAG3  = -WerrorWFLAG4  = -Wstrict-prototypesWFLAG5  = -Wmissing-prototypesWFLAGS  = ${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 macOSDEBRIS = a.out core *~ *.dSYMRM_FR  = rm -fr
clean:${RM_FR} ${FILES.o} ${PROGRAM} ${DEBRIS}

准则

规则只能由专家打破,并且只有在有充分理由的情况下:

  • 头文件只包含extern个变量声明-从不static或不合格的变量定义。

  • 对于任何给定的变量,只有一个头文件声明它(SPOT-#36825;的真理”

  • 源文件永远不会包含变量的extern声明-源文件始终包含声明它们的(唯一)标头。

  • 对于任何给定的变量,只有一个源文件定义了该变量,最好也初始化它。(虽然没有必要显式初始化为零,它没有坏处,可以做一些好事,因为只有一个特定的初始化定义程序中的全局变量)。

  • 定义变量的源文件还包括确保定义和声明是一致的。

  • 函数永远不需要使用extern声明变量。

  • 尽可能避免全局变量-使用函数代替。

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

如果你不是一个有经验的C程序员,你可以(也许#36825;此停止阅读

不是定义全局变量的好方法

使用一些(实际上是许多)C编译器,您可以逃脱也称为变量的“通用”定义。这里的'Common'指的是Fortran中用于共享的技术源文件之间的变量,使用(可能命名的)COMMON块。这里发生的是,许多文件中的每一个都提供了一个暂定的变量的定义。只要不超过一个文件提供初始化定义,然后各种文件最终共享一个共同的单一定义变量:

file10. c

#include "prog2.h"
long l;   /* Do not do this in portable code */
void inc(void) { l++; }

file11. c

#include "prog2.h"
long l;   /* Do not do this in portable code */
void dec(void) { l--; }

file12. c

#include "prog2.h"#include <stdio.h>
long l = 9;   /* Do not do this in portable code */
void put(void) { printf("l = %ld\n", l); }

此技术不符合C标准的字母和“一个定义规则”-它是官方未定义的行为:

J.2未定义的行为

使用带有外部链接的标识符,但在程序中存在标识符的外部定义不存在,或未使用标识符,并且存在多个外部标识符的定义(6.9)。

§6.9外部定义§5

外部定义是一个外部声明,也是一个函数的定义(内联定义除外)或对象。如果使用外部链接声明的标识符在表达式(除了作为sizeofsizeof操作数的一部分_Alignof运算符,其结果是一个整数常量),位于整个程序应该只有一个外部定义标识符;否则,不应超过161)

161)因此,如果使用外部链接声明的标识符在表达式中不使用,不需要外部定义它。

然而,C标准也在信息性附件J中将其列为常见扩展.

J.5.11多个外部定义

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

由于并不总是支持这种技术,因此最好避免使用它,特别是如果您的代码需要可移植。使用这种技术,您也可以最终得到无意的类型双关语。

如果上面的一个文件将l声明为double而不是doublelong,C的类型不安全链接器可能不会发现不匹配。如果你在一台64位longdouble的机器上,你甚至不会得到警告;在32位long和64位double的机器上,你可能会收到关于不同尺寸的警告-链接器将使用最大的大小,正如Fortran程序将使用任何普通块的最大大小。

请注意,2020-05-07发布的GCC 10.1.0更改了要使用的默认编译选项#0这意味着默认情况下,上面的代码不再链接,除非您覆盖默认为-fcommon(或使用属性等-请参阅链接)。


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

程序2. 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使用prog2.cfile10.cfile11.cfile12.cprog2.h

警告

正如这里的评论所指出的,正如我对类似问题的回答所述问题,使用多个全局变量的定义导致未定义的行为(J.2;§6.9),这是标准的说法“任何事情都可能发生”。可能发生的事情之一是程序表现得像你J.5.11大约说,“你可能更幸运比你应得的”。但是依赖于外部变量的多个定义的程序-有或没有明确的'extern'关键字-不是严格的符合程序,不能保证在任何地方工作。等价地:它包含一个可能会或可能不会显示自己的bug。

违反准则

当然,有很多方法可以打破这些准则。偶尔,可能有一个很好的理由来打破指导方针,但是这种情况是非常罕见的。

faulty_headerH

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

注1:如果标头定义的变量没有extern关键字,然后每个包含标题的文件都会创建一个暂定定义的变量。如前所述,这通常会起作用,但C标准不会保证它会工作。

broken_headerH

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

注2:如果标头定义并初始化变量,则仅给定程序中的一个源文件可以使用标头。由于标题主要用于共享信息,因此有点愚蠢

创建一个只能使用一次的。

seldom_correctH

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

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

例如,如果变量实际上是一个复杂数组,这可能会导致极端重复的代码。它可以,偶尔,是一个#36825;的方式来达到一定的效果,但这是非常不寻常的。


总结

使用我首先展示的标题技术。它工作可靠,无处不在。请特别注意,声明global_variable的标头是包含在使用它的每个文件中-包括定义它的文件。这确保了一切都是自洽的。

声明和定义函数也会出现类似的问题-类似的规则适用。但问题是关于变量的,所以我保留了只回答变量。

原答案结束

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


后期主要加法

避免代码重复

有时(合法地)提出的一个问题是描述了“标题中的声明,源中的定义”机制这里有两个文件要保持同步-标题和来源。这通常伴随着一个观察可以使用宏,以便标头提供双重职责-通常声明变量,但当特定宏设置在包含头,它定义了变量。

另一个问题可能是需要在每个变量中定义变量一些“主程序”。这通常是一个虚假的关注;您可以简单地引入一个C源文件来定义变量和链接每个程序生成的目标文件。

一个典型的方案是这样工作的,使用原始的全局变量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++);}

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

程序3. 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使用prog3.cfile1a.cfile2a.cfile3a.hprog3.h

变量初始化

这个方案的问题是它没有提供全局变量的初始化。使用C99或C11和变量参数列出宏,您也可以定义一个宏来支持初始化。(使用C89并且不支持宏中的变量参数列表,因此没有处理任意长初始化器的简单方法。)

文件3b. 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 });

反转#if#else块的内容,固定bug由DenisKniazhev

文件1b. 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; }

文件2b. 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;}

很明显,奇怪的结构的代码不是你通常想要的写,但它说明了这一点。第一个参数到第二个INITIALIZER的调用是{ 41和剩余的参数(本例中为单数)为43 }。没有C99或类似的支持对于宏的变量参数列表,需要包含逗号是非常有问题的。

每个包含正确的标头file3b.h(而不是fileba.hDenisKniazhev


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

程序

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

项目4. 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使用prog4.cfile1b.cfile2b.cprog4.hfile3b.h

标题守卫

任何标头都应该受到保护以防止重新包含,因此类型定义(枚举、结构或联合类型,或通常的typedef)不造成问题。标准技术是包裹身体头保护中的头,例如:

#ifndef FILE3B_H_INCLUDED#define FILE3B_H_INCLUDED
...contents of header...
#endif /* FILE3B_H_INCLUDED */

标头可能间接包含两次。例如,如果file4b.h包含file3b.h,用于未显示的类型定义,file1b.c需要同时使用标头file4b.hfile3b.h,然后你有更棘手的问题要解决显然你可能会修改标题列表只包含file4b.h。但是,您可能不意识到内部依赖关系-理想情况下,代码应该,继续工作

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

因此,您需要最多包含一次file3b.h的主体声明,定义最多一次,但您可能需要两者在单个翻译单元(TU-源文件和文件的组合它使用的标题)。

具有变量定义的多重包含

但是,它可以在不太不合理的约束下完成。让我们引入一组新的文件名:

  • external.h用于EXTERN宏定义等。

  • file1c.h来定义类型(特别是struct oddballoddball_struct的类型)。

  • file2c.h定义或声明全局变量。

  • file3c.c定义全局变量。

  • file4c.c简单地使用全局变量。

  • file5c.c这表明您可以声明然后定义全局变量。

  • file6c.c这表明您可以定义然后(尝试)声明全局变量。

在这些示例中,file5c.cfile6c.c直接包含标头file2c.h几次,但这是最简单的方法来显示机制有效。这意味着如果标题被间接包含#36825;二次,就安全了

这个工作的限制是:

  1. 定义或声明全局变量的标头本身可能不是定义任何类型。

  2. 在定义变量之前,您定义宏DEFINE_VARIABLES。

  3. 定义或声明变量的头文件具有风格化的内容。

外部. 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 */

文件1c. 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 */

文件2c. 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 */

文件3c. 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; }

下一个源文件完成prog5prog6prog7的源代码(提供主程序):

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使用prog5.cfile3c.cfile4c.cfile1c.hfile2c.hexternal.h

  • prog6使用prog5.cfile5c.cfile4c.cfile1c.hfile2c.hexternal.h

  • prog7使用prog5.cfile6c.cfile4c.cfile1c.hfile2c.hexternal.h


此方案避免了大多数问题。只有在定义变量(例如file2c.h)的标头包含在另一个定义变量的标头(例如file7c.h)。没有除了“不要这样做”之外,还有其他简单的方法。

您可以通过将file2c.h修改为file2d.h

#请求参数

文件2d. 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 */

问题变成了“标题是否应该包含#undef DEFINE_VARIABLES?”如果您从标头中省略它并将任何定义调用包装为#define#undef

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

在源代码中(因此标头永远不会改变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 */

这有点复杂,但似乎是安全的(使用file2d.hfile2d.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; }

文件

/* 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 */

文件

/* 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; }

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

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使用prog8.cfile7c.cfile9c.c

  • prog9使用prog8.cfile8c.cfile9c.c


然而,这些问题在实践中相对不太可能发生,特别是如果你把标准的建议

避免全局变量


这个展览遗漏了什么吗?

_Confession_:这里概述的“避免重复代码”方案是开发是因为该问题影响了我处理的一些代码(但不拥有),并且是对第一部分中概述的计划的一个琐碎的担忧答案。然而,最初的方案只剩下两个修改以保留变量定义和声明的地方同步,这是向前迈出的一大步分散在整个代码库中的声明(这真的很重要当总共有数千个文件时)。但是,名称为'fileNc.[ch]'的文件(加上'external. h'和'externdef. h')表明它可以工作。显然,这并不难创建一个头生成器脚本来为您提供标准化模板用于定义和声明头文件的变量。

NB这些是玩具程序,代码几乎不足以制作它们略微有趣。有重复的例子可以删除,但不是为了简化教学解释。(例如:prog5.cprog8.c之间的区别是名称包含的标题之一。这将是可能的重新组织代码,使main()函数不重复,但它会隐藏更多的比它显示)。

我喜欢将外部变量视为您对编译器的承诺。

当遇到extern时,编译器只能找出它的类型,而不是它“居住”的位置,因此无法解析引用。

你告诉它,“相信我。在链接时,此引用将是可解析的。”

对extern的正确解释是你告诉编译器一些事情。你告诉编译器,尽管现在不存在,但声明的变量会以某种方式被链接器找到(通常是在另一个对象(文件)中)。然后链接器将是幸运的家伙,找到所有内容并将其组合在一起,无论你是否有一些extern声明。

另一方面,函数默认具有全局作用域。因此你不必明确地向编译器说明“看,伙计……你可能在这里找到这个函数的定义”。对于一个包含其声明的文件的函数就足够了。(你实际上调用的文件是头文件)。例如,考虑以下两个文件:
example. c

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

示例1. c

int a = 5;

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

步骤1)cc-o ex example. c example ple1. c第2步)。/ex

您将得到以下输出:a的值<5>

首先,extern关键字不是用来定义变量的;而是用来声明变量的。我可以说extern是一个储存类别,而不是数据类型。

extern用于让其他C文件或外部组件知道该变量已经在某个地方定义了。示例:如果您正在构建库,无需在库本身的某个地方强制定义全局变量。库将直接编译,但在链接文件时,它会检查定义。

extern关键字与变量一起使用,用于将其标识为全局变量。

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

extern允许程序的一个模块访问在程序的另一个模块中声明的全局变量或函数。您通常会在头文件中声明extern变量。

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

使用extern,因此一个first.c文件可以完全访问另一个second.c文件中的全局参数。

extern可以在first.c文件或first.c包含的任何头文件中声明。

GCC ELFLinux实施

其他答案已经涵盖了语言使用方面的观点,所以现在让我们看看它是如何在这个实现中实现的。

main. 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);}

编译和反编译:

gcc -c main.creadelf -s main.o

输出包含:

Num:    Value          Size Type    Bind   Vis      Ndx Name9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 not_extern_int12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND extern_int

System V ABI更新ELF规范“符号表”一章解释说:

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

这基本上是C标准赋予extern变量的行为。

从现在开始,制作最终程序是链接器的工作,但extern信息已经从源代码中提取到目标文件中。

在GCC 4.8上测试。

C++17个内联变量

在C++17中,您可能希望使用内联变量而不是外部变量,因为它们使用简单(可以在头文件上定义一次)并且更强大(支持Constexr)。请参见:在C和C++中“const静态”是什么意思?

extern仅仅意味着变量在其他地方定义(例如,在另一个文件中)。

使用xc8,您必须小心声明变量作为每个文件中的相同类型,错误地,在一个文件中声明int,在另一个文件中声明char。这可能会导致变量损坏。

这个问题在15年前的一个微芯片论坛上得到了很好的解决/*超文本传输协议:www.htsoft.com/"showflat.php/Cat/0/Number/18766/an/0/page/0#18766"

但这种联系似乎不再有效…

所以我会很快试着解释它;创建一个名为global. h的文件。

其中声明如下

#ifdef MAIN_C#define GLOBAL/* #warning COMPILING MAIN.C */#else#define GLOBAL extern#endifGLOBAL unsigned char testing_mode; // example var used in several C files

现在在文件main. c中

#define MAIN_C 1#include "global.h"#undef MAIN_C

这意味着在main. c中,变量将被声明为unsigned char

现在在其他文件中,只需包含global. h即可将其声明为extern对于该文件

extern unsigned char testing_mode;

但它将被正确声明为unsigned char

老论坛的帖子可能更清楚地解释了这一点。但在使用编译器时,这是一个真正的潜在gotcha它允许您在一个文件中声明变量,然后在另一个文件中将其声明为外部类型。与相关的问题如果你在另一个文件中声明testing_mode为int它会认为这是一个16位var并覆盖ram的其他部分,可能会破坏另一个变量。难以调试!

                 declare | define   | initialize |----------------------------------
extern int a;    yes          no           no-------------int a = 2019;    yes          yes          yes-------------int a;           yes          yes          no-------------

声明不会分配内存(必须定义变量才能分配内存),但定义会。这只是关于extern关键字的另一个简单视图,因为其他答案真的很棒。

我使用的一个非常简短的解决方案允许头文件包含对象的extern引用或实际实现。实际包含对象的文件只是#define GLOBAL_FOO_IMPLEMENTATION。然后当我向该文件添加新对象时,它也会显示在该文件中,而无需复制和粘贴定义。

我在多个文件中使用这种模式。所以为了保持尽可能的自包含,我只是在每个标题中重用单个GLOBAL宏。我的标题如下所示:

//file foo_globals.h#pragma once#include "foo.h"  //contains definition of foo
#ifdef GLOBAL#undef GLOBAL#endif
#ifdef GLOBAL_FOO_IMPLEMENTATION#define GLOBAL#else#define GLOBAL extern#endif
GLOBAL Foo foo1;GLOBAL Foo foo2;

//file main.cpp#define GLOBAL_FOO_IMPLEMENTATION#include "foo_globals.h"
//file uses_extern_foo.cpp#include "foo_globals.h

简而言之,extern意味着变量在其他模块中定义,并且在链接时知道其地址。编译器不会在当前模块中保留内存,并且知道变量类型。要理解extern,至少要有一点汇编器经验。

符号(var或函数)前的extern关键字告诉链接器它(源文件)使用了一个外部符号。这可以通过在这样的目标文件(. o)上运行nm-a来看到,该文件使用或为extern var赋值(记得像这样在顶部声明一个extern符号extern int x或者更好,在vars和函数可以没有extern之前使用带有extern的头文件;然后在main中像这样为它赋值x=5;),我找到了针对这样一个extern var(符号)的未定义bss信息(写的字母B)。这意味着x仍然未解析,将在ld运行时(在链接时间)解析。

为什么总是在头文件中使用extern?如果我不使用extern,只是声明int x,声明变得有点强,没有extern,这会在每个包含标题的源中重新定义相同的变量,有效地隐藏原始变量。这个var在源代码中,这个没有extern的var decl在标题阴影中(它并没有完全阴影,它在每个包含只有int x的标题的源代码中重新定义了一个全局变量x,没有extern,当我包含这样的标题并尝试从这些文件编译. o时,每个. o都有自己的全局变量x定义,它包含在没有extern的标题中,并且在链接时,我得到变量或符号x的错误多重定义)一个重要的变量定义在源文件的其他地方。重要!有必要在标头中的vars之前使用extern。默认情况下,函数已经是extern。