提问



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


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

最佳参考


使用extern仅适用于您正在构建的程序
由多个源文件链接在一起组成,其中一些
例如,在源文件file1.c中定义的变量需要
在其他源文件中引用,例如file2.c


了解定义 a之间的区别非常重要
变量和声明 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 

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


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





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


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


prog1.h



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


prog1.c的



#include "file3.h"
#include "prog1.h"
#include 

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的生成文件。
它适用于转弯后产生的make的大多数版本
千禧年
它与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}





准则



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



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

  • 对于任何给定的变量,只有一个头文件声明它(SPOT -
    单点真相)。

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

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

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

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

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



这个答案的源代码和文字可以在我的网站上找到
??????SOQ(堆栈溢出问题)
??????GitHub上的存储库
??????SRC/SO-0143-3204
??????子目录。
[215] [216]


如果你不是一位经验丰富的C程序员,你可以(或许也许
???????应该停止阅读。



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



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


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 

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

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


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



?? J.2未定义的行为 [217]

??
??使用具有外部链接的标识符,但是在程序中
??不完全存在标识符的一个外部定义,或
??标识符未使用且存在多个外部标识符
??标识符的定义(6.9)。

??
??§6.9外部定义?5 [218]

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

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



但是,C标准也将其列为资料性附件J中的一个
共同扩展。[219]



??J.5.11多个外部定义[220]

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



由于并不总是支持此技术,因此最好避免使用
使用它,尤其是如果您的代码需要可移植。
使用这种技术,您最终也会得到无意识的类型
双关语。
如果其中一个文件声明idouble而不是int
C型不安全接头可能不会发现不匹配。
如果你使用的是64位intdouble的机器,你甚至都不会
得到警告;在具有32位int和64位double的机器上,你d
可能会收到关于不同大小的警告 - 链接器会
使用最大的大小,就像Fortran程序一样
任何常见块的最大尺寸。





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


prog2.h



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


prog2.c



#include "prog2.h"
#include 

int main(void)
{
    inc();
    put();
    dec();
    put();
    dec();
    put();
}



  • prog2使用prog2.cfile10.cfile11.cfile12.cprog2.h






警告



正如在这里的评论中所指出的那样,并且在我对类似的答案中所述
问题,使用多个
全局变量的定义导致未定义的行为(J.2;
§6.9),这是说任何事情都可能发生的标准方式。
可能发生的一件事是该程序的行为与您一样
期望;和J.5.11大致说,你可能更经常幸运
比你应得的。
但是一个依赖于外部变量的多个定义的程序
- 有或没有明确的extern关键字 - 不严格
符合计划,并不保证在任何地方工作。
同等地:它包含一个可能会或可能不会显示的错误。


违反指南



当然,有很多方法可以打破这些指导方针。
偶尔,可能有理由违反指南,但是
这种场合极不寻常。


faulty_header.h



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


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


broken_header.h



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


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


seldom_correct.h



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


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


例如,如果变量实际上是一个复杂的数组,那么这可能会导致
极端重复的代码。它偶尔可以是一个
明智的方式来实现一些效果,但这是非常不寻常的。





摘要



使用我首先展示的标题技术。
它可靠,无处不在。
特别要注意,声明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 

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





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


prog3.h



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


prog3.c



#include "file3a.h"
#include "prog3.h"
#include 

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并且不支持宏中的变量参数列表,没有
处理任意长的初始化程序的简单方法。)


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


#if#else块的反向内容,修复由...识别的错误
Denis Kniazhev



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 

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.h)
Denis Kniazhev






接下来的两个文件完成了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 

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






Header Guards



应该保护任何标头以防止重新包含,以便进行类型化
定义(枚举,结构或联合类型,或通常是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好几次,但这是最简单的表现方式
机制有效。这意味着如果标题间接包含在内
两次,这也是安全的。


这项工作的限制是:



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

  2. 在您包含应定义变量的标头之前,
    你定义宏DEFINE_VARIABLES。

  3. 定义或声明变量的标题具有程式化的内容。



external.h



/*
** This header must not contain header guards (like  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 

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 

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:


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


问题变成如果标题包含#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  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; }


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





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


prog8.c



#include "file2d.h"
#include 

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 

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






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


避免全局变量






这个博览会是否遗漏了什么?


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


NB 这些玩具程序只有几乎没有足够的代码来制作它们
略有意思。在这些例子中有重复
可以删除,但不是为了简化教学解释。
(例如:prog5.cprog8.c之间的区别是名称
其中一个标题包含在内。有可能
重新组织代码,以便main()函数不重复,但是
它隐藏的不仅仅是它所揭示的。)

其它参考1


extern变量是在另一个翻译单元中定义的变量的声明(由于sbi用于校正)。这意味着变量的存储空间分配在另一个文件中。


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


完整样本:


$ cat test1.c 
int test1_var = 5;
$ cat test2.c
#include 

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

其它参考2


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


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


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

其它参考3


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


遇到extern时,编译器只能找到它的类型,而不是它存在的位置,因此它无法解析引用。


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

其它参考4


extern告诉编译器要相信这个变量的内存是在别处声明的,所以它不会尝试分配/检查内存。


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


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

其它参考5


添加extern会将变量定义转换为变量声明。请参阅此主题,了解声明和定义之间的区别。

其它参考6


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

其它参考7


在C中,文件中的变量表示example.c具有本地范围。编译器期望变量的定义在同一个文件example.c中,当它找不到相同的时候,它会抛出一个错误。另一方面,一个函数默认具有全局范围。因此,您不必向编译器明确提及看起来很粗鲁......您可能会在这里找到此函数的定义。对于包含包含其声明的文件的函数就足够了。(实际上称为头文件的文件)。
???例如,考虑以下2个文件:

?example.c


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


example1.c


int a = 5;


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


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


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

其它参考8


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



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


其它参考9


GCC ELF Linux实施


main.c:


#include 

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.c
readelf -s main.o


输出包含:


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


System V ABI更新ELF规范符号表一章解释:[225]



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



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


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


在GCC 4.8上测试。

其它参考10


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


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

其它参考11


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


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

其它参考12


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


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

其它参考13


extern仅表示变量在别处定义(例如,在另一个文件中)。