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

问题描述 投票:907回答:17

我知道,在C全局变量有时有extern关键字。什么是extern变量?是什么样的声明?什么是它的范围是什么?

这是涉及到跨源文件的共享变量,但怎么才能做到准确?我在哪里使用extern

c global-variables extern
17个回答
1658
投票

使用extern仅仅是相关的,当你正在构建的方案包括链接在一起的多个源文件,其中的一些变量的定义,例如,在源文件file1.c需要在其他的源文件,如file2.c被引用。

重要的是要understand the difference between defining a variable and declaring a variable

  • 甲变量声明时,编译器被告知一个变量存在(这是它的类型);它不会在该点的变量分配存储。
  • 当编译器分配该变量的存储变量的定义。

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

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

声明和定义全局变量的清洁,可靠的方法是使用一个头文件来包含变量的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;编译器不关心 - 最终,我也不只要你保持一致,至少一个源文件中。

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使用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(堆栈溢出问题)在src/so-0143-3204子目录在GitHub仓库中。

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

不太好方法来定义全局变量

对于某些(事实上,许多)C编译器,你可以逃脱什么叫做变量的“共同”的定义了。 “普通”,在这里,是指用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); }

这种技术不符合C标准的信和“一个定义规则” - 这是正式未定义的行为:

J.2 Undefined behavior

与外部链接的标识符被使用,但在节目不存在恰好一个外部定义的标识符,或者不使用所述标识符并且存在多个外部定义为标识符(6.9)。

§6.9 External definitions ¶5

外部定义为外部声明,这也是一个功能(比内联定义其他)或对象的定义。如果有外部链接声明的标识符在表达式中被使用(不是作为sizeof_Alignof操作,其结果是一个整常数的操作数的一部分的其它),某处在整个程序须有使标识符恰好一个外部定义;否则,不得有超过one.161)

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

然而,C标准还列出了它在信息附件J作为Common extensions之一。

J.5.11 Multiple external definitions

可能有一个对象的标识符,具有或不具有明确使用关键字的extern的多于一个的外部定义;如果定义不同意,或一个以上的初始化,行为是不确定的(6.9.2)。

因为这种技术并不总是支持,最好避免使用它,特别是如果你的代码需要移植。使用这种技术,你也可以与无意类型双关结束。如果文件中的一个声明i作为double而不是作为一个int,C的类型不安全的接头可能不会发现的不匹配。如果你有64位intdouble一台机器上,你甚至没有得到一个警告; 32位int和64位double一台机器上,你可能会得到一个有关不同大小警告 - 链接器将使用的最大尺寸,正如Fortran程序会采取任何公共块的最大尺寸。


接下来的两个文件完成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使用prog2.cfile10.cfile11.cfile12.cprog2.h

Warning

正如评论这里要注意,并在我的答案陈述了类似的question,使用多个定义为一个全局变量导致未定义行为(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头被包含在使用它的每一个文件 - 包括定义它的一个。这确保一切都是自洽的。

出现与声明和定义功能类似的担心 - 类似的规则。但问题是关于具体的变量,所以我已经把答案唯一变量。

End of Original Answer

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


后期主要加法

Avoiding Code Duplication

一个问题是有时(和合法)提出了关于“页眉声明,在源定义”这里所描述的机制是有两个文件被保持同步 - 标头和源极。这通常是随访与一个宏可用于使得头部具有双重职责的观察 - 通常声明变量,但是当包括报头的前面设置一个特定的宏,它定义了变量代替。

另一个担心是,该变量需要在每一批的“主程序”来定义。这通常是一个虚假的关注;可以简单地介绍一个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来源:

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使用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 <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.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 <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几次头,但是这是表明该机制的工作最简单的方法。这意味着,如果头球攻门被间接地包含了两次,它也将是安全的。

对于这项工作的限制有:

  1. 头定义或声明的全局变量本身可以不定义任何类型。
  2. 你包括应该定义变量头之前,您定义宏DEFINE_VARIABLES。
  3. 定义或声明的变量的所述标头程式化内容。

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

下一个源文件完成了源(提供了一个主程序)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.hfile2d.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 <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.h,随着#undef DEFINE_VARIABLES没有file2d.h)。

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

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

Avoid global variables


这是否博览会错过了什么?

自白:这里列出的“避免重复的代码”计划的开发,因为这个问题影响了一些代码,我的工作(但不拥有),并与在回答第一部分概述了方案中的琐碎问题。然而,原计划让你只用两个地方进行修改,以保持变量定义和同步的声明,这是向前迈进了一大步,在具有分散在整个代码库exernal变量声明(当有成千上万的文件中总共其中真正的问题) 。然而,在与名fileNc.[ch](加external.hexterndef.h)的文件中的代码表明,它可以进行工作。显然,这不会是努力创造一个头生成器脚本来给你的标准化模板变量定义和声明的头文件。

NB这些是只是勉强足够的代码玩具程序,使它们勉强有趣。有可能被删除的例子中重复,但不是为了简化教学的解释。 (例如:prog5.cprog8.c之间的差值所包含的报头中的一个的名称将是可能重新组织,使得没有重复main()函数的代码,但它会掩盖超过它显露出来。)。


7
投票

extern关键字使用具有其识别为全局变量的变量。

这也代表你可以使用使用extern关键字声明的变量在任何文件中,虽然其声明/在其他文件中定义。


6
投票

GCC ELF Linux实现

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.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 Update ELF spec“符号表”一章解释说:

SHN_UNDEF此部分表索引表示符号是未定义的。当链接器链接与其他定义显示的符号这个目标文件,该文件对符号的引用将链接到实际的定义。

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

从现在开始,它是连接器,使最终方案的工作,但extern信息已经从源代码到目标文件中提取。

测试在GCC 4.8。

C ++ 17个内嵌变量

在C ++ 17,您可能需要使用内联变量而不是那些的extern,因为它们使用简单(可在头只定义一次),更强大(支持constexpr)。请参阅:What does 'const static' mean in C and C++?


5
投票

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

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


5
投票

extern仅仅意味着一个变量在别处定义(例如,在另一文件)。


4
投票

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

extern是用来让其他C文件或外部组件知道这个变量已经某处定义。例如:如果你正在建设一个图书馆,无需定义的全局变量在库本身强制性地方。该库将被直接编译,但在链接的文件,它会检查的定义。


3
投票

extern使用这样一个first.c文件可以在另一个second.c文件完全访问全局参数。

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


2
投票

随着xc8你必须要小心,在每个文件中的变量声明为同一类型,你可以,错误,申报的东西在一个文件中的intchar在另说。这可能会导致变量腐败。

这个问题在微芯片论坛,优雅的解决大约15年前/ *请参阅 “HTTP:www.htsoft.com”/ /“论坛/所有/ showflat.php /猫/ 0 /号码/ 18766 / AN / 0 /页/ 0#18766"

但这个链接似乎不再工作...

所以我;会迅速试图解释它;创建一个名为global.h文件。

在其声明如下

#ifdef MAIN_C
#define GLOBAL
 /* #warning COMPILING MAIN.C */
#else
#define GLOBAL extern
#endif
GLOBAL 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 unsigned char testing_mode;

但它会被正确声明为unsigned char

老论坛的帖子可能多一点解释清楚这一点。但是,使用编译器,可以让你在一个文件中声明一个变量,然后的extern声明为不同的类型在另一个时,这是一个真正的潜力gotcha。与之相关的问题是,如果你说声明作为testing_mode在另一个文件中的int会认为这是一个16位的VAR和覆盖RAM的其他部分,有可能破坏另一个变量。很难调试!


0
投票

非常短的溶液我使用,以允许头文件包含一个对象的外部引用的或实际的实现。实际上包含对象的文件只是做#define GLOBAL_FOO_IMPLEMENTATION。然后,当我添加一个新的对象到这个文件在该文件中还没有我不必复制和粘贴的定义出现了。

我使用多个文件这种模式。所以为了让事情自我包含的可能,我只是在重复使用每个头单全局宏。我的标题是这样的:

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

121
投票

一个extern变量是一个声明(感谢SBI的校正)的变量中的哪一个在另一个转换单元中定义。这意味着该变量的存储在另一个文件中进行分配。

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

完整的示例:

$ 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

38
投票

EXTERN是你用来声明变量本身驻留在另一个翻译单位关键字。

所以,你可以决定在翻译单元中使用一个变量,然后从另外一个访问它,然后在第二个声明它为外部和符号将被链接器解决。

如果你不把它声明为extern你会得到2个变量命名相同,但都没有关系,且变量的多个定义错误。


26
投票

我喜欢把一个外部变量,那您对编译器的承诺。

当遇到一个外部的编译器只能找到它的类型,而不是在那里“生命”,所以它不能解析引用。

你告诉它,“相信我。在链接时这个参考值将是解析的。”


18
投票

的extern告诉编译器信任你,为了这个变量存储在别处声明的,因此它不尝试分配/检查内存。

因此,你可以编译具有参考一个外部文件,但如果内存没有地方申报不能链接。

有用的全局变量和库,但危险的,因为连接不类型检查。


15
投票

添加一个extern把一个变量定义成一个变量声明。见this thread至于什么是声明和定义之间的差异。


11
投票

EXTERN的正确解释是,你告诉一些编译器。你告诉的是,尽管不存在,现在,声明的变量会以某种方式被链接器(通常在另一个对象(文件))找到的编译器。然后连接器将是幸运的人找到一切,把它在一起,你是否有一些外部声明或没有。


8
投票

在C文件中的变量说example.c给出局部范围。编译器预计,变量将有它的定义相同的文件example.c内,当它不找到相同的,那就扔在另一方面,一个error.A功能在默认情况下全球范围内。因此,你不必明确提及编译器“看猪头...你可以在这里找到这个函数的定义”。对于包括其中包含的声明是不够的文件。(你实际上调用一个头文件中的文件)的功能。例如,考虑以下2个文件: example.c

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

example1.c

int a = 5;

现在,当你两个文件编译在一起,使用下面的命令:

步骤1)CC -o前example.c example1.c步骤2)./前

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


8
投票
                 declare | define   | initialize |
                ----------------------------------

extern int a;    yes          no           no
-------------
int a = 2019;    yes          yes          yes
-------------
int a;           yes          yes          no
-------------

声明将不分配存储器(该变量必须对存储器分配被定义),但定义将。因为其他的答案是真正伟大这是extern关键字只是一个简单的观点。

© www.soinside.com 2019 - 2024. All rights reserved.