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

发布于 2024-08-05 11:08:55 字数 148 浏览 5 评论 0原文

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

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

I know that global variables in C sometimes have the extern keyword. What is an extern variable? What is the declaration like? What is its scope?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(19

往昔成烟 2024-08-12 11:08:55

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

了解定义
变量并声明 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
声明;编译器不在乎——最终,我也不在乎
只要你保持一致,至少在源文件中是一致的。

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 的 makefile。
它将与自本轮以来生成的大多数版本的 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 声明 - 从不
    静态或非限定变量定义。

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

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

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

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

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

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

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

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

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

使用一些(实际上是很多)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 未定义行为< /strong>


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

§6.9 外部定义 ¶5< /strong>


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

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

然而,C 标准还在资料性附录 J 中将其列为
常用扩展

J.5.11 多个外部定义

对于以下标识符可能有多个外部定义
一个对象,无论是否显式使用关键字 extern;如果
定义不一致,或者多个定义被初始化,
行为未定义 (6.9.2)。

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

如果上述文件之一将 l 声明为 double 而不是 a
long,C 的类型不安全链接器可能不会发现不匹配。
如果您使用的是 64 位 longdouble 的机器,您甚至不需要
收到警告;在具有 32 位 long 和 64 位 double 的机器上,
您可能会收到有关不同大小的警告 - 链接器
将使用最大的大小,就像 Fortran 程序将使用
任何公共块的最大尺寸。

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


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

警告

正如此处的评论中所述,以及我对类似问题的回答中所述
问题,使用多个
全局变量的定义会导致未定义的行为(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 <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.c< /code>、prog4.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; }


下一个源文件完成prog5的源代码(提供主程序),prog6prog7

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

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.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 <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_: The 'avoiding duplicated code' scheme outlined here was
developed because the issue affects some code I work on (but don't own),
and is a niggling concern with the scheme outlined in the first part of
the answer. However, the original scheme leaves you with just two
places to modify to keep variable definitions and declarations
synchronized, which is a big step forward over having exernal variable
declarations scattered throughout the code base (which really matters
when there are thousands of files in total). However, the code in the
files with the names `fileNc.[ch]` (plus `external.h` and `externdef.h`)
shows that it can be made to work. Clearly, it would not be hard to
create a header generator script to give you the standardized template
for a variable defining and declaring header file.

注意这些都是玩具程序,只有足够的代码来制作它们
有点有趣。示例中有重复之处
可以删除,但并不是为了简化教学解释。
(例如:prog5.cprog8.c 之间的区别是名称
包含的标头之一。有可能
重新组织代码,使 main() 函数不再重复,但是
它隐藏的东西比它揭示的东西多。)

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.

It is important to understand the difference between defining a
variable and declaring a
variable
:

  • A variable is declared when the compiler is informed that a
    variable exists (and this is its type); it does not allocate the
    storage for the variable at that point.

  • A variable is defined when the compiler allocates the storage for
    the variable.

You may declare a variable multiple times (though once is sufficient);
you may only define it once within a given scope.
A variable definition is also a declaration, but not all variable
declarations are definitions.

Best way to declare and define global variables

The clean, reliable way to declare and define global variables is to use
a header file to contain an extern declaration of the variable.

The header is included by the one source file that defines the variable
and by all the source files that reference the variable.
For each program, one source file (and only one source file) defines the
variable.
Similarly, one header file (and only one header file) should declare the
variable.
The header file is crucial; it enables cross-checking between
independent TUs (translation units — think source files) and ensures
consistency.

Although there are other ways of doing it, this method is simple and
reliable.
It is demonstrated by file3.h, file1.c and 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:

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.

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.

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

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

  • For any given variable, only one header file declares it (SPOT —
    Single Point of Truth).

  • A source file never contains extern declarations of variables —
    source files always include the (sole) header that declares them.

  • For any given variable, exactly one source file defines the variable,
    preferably initializing it too. (Although there is no need to
    initialize explicitly to zero, it does no harm and can do some good,
    because there can be only one initialized definition of a particular
    global variable in a program).

  • The source file that defines the variable also includes the header to
    ensure that the definition and the declaration are consistent.

  • A function should never need to declare a variable using extern.

  • Avoid global variables whenever possible — use functions instead.

The source code and text of this answer are available in my
SOQ (Stack Overflow Questions)
repository on GitHub in the
src/so-0143-3204
sub-directory.

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

Not so good way to define global variables

With some (indeed, many) C compilers, you can get away with what's
called a 'common' definition of a variable too.
'Common', here, refers to a technique used in Fortran for sharing
variables between source files, using a (possibly named) COMMON block.
What happens here is that each of a number of files provides a tentative
definition of the variable.
As long as no more than one file provides an initialized definition,
then the various files end up sharing a common single definition of the
variable:

file10.c

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

This technique does not conform to the letter of the C standard and the
'one definition rule' — it is officially undefined behaviour:

J.2 Undefined behavior

An identifier with external linkage is used, but in the program there
does not exist exactly one external definition for the identifier, or
the identifier is not used and there exist multiple external
definitions for the identifier (6.9).

§6.9 External definitions ¶5

An external definition is an external declaration that is also a
definition of a function (other than an inline definition) or an
object.
If an identifier declared with external linkage is used in an
expression (other than as part of the operand of a sizeof or
_Alignof operator whose result is an integer constant), somewhere in
the entire program there shall be exactly one external definition for
the identifier; otherwise, there shall be no more than
one.161)

161) Thus, if an identifier declared with external linkage
is not used in an expression, there need be no external definition for
it.

However, the C standard also lists it in informative Annex J as one of
the Common extensions.

J.5.11 Multiple external definitions

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

Because this technique is not always supported, it is best to avoid
using it, especially if your code needs to be portable.
Using this technique, you can also end up with unintentional type
punning.

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

Note that GCC 10.1.0, which was released on 2020-05-07, changes the
default compilation options to use
-fno-common, which means
that by default, the code above no longer links unless you override the
default with -fcommon (or use attributes, etc — see the link).


The next two files complete the source for prog2:

prog2.h

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.

Warning

As noted in comments here, and as stated in my answer to a similar
question, using multiple
definitions for a global variable leads to undefined behaviour (J.2;
§6.9), which is the standard's way of saying "anything could happen".
One of the things that can happen is that the program behaves as you
expect; and J.5.11 says, approximately, "you might be lucky more often
than you deserve".
But a program that relies on multiple definitions of an extern variable
— with or without the explicit 'extern' keyword — is not a strictly
conforming program and not guaranteed to work everywhere.
Equivalently: it contains a bug which may or may not show itself.

Violating the guidelines

There are, of course, many ways in which these guidelines can be broken.
Occasionally, there may be a good reason to break the guidelines, but
such occasions are extremely unusual.

faulty_header.h

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.

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.

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.

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.

Similar concerns arise with declaring and defining functions —
analogous rules apply.
But the question was about variables specifically, so I've kept the
answer to variables only.

End of Original Answer

If you're not an experienced C programmer, you probably should stop reading here.


Late Major Addition

Avoiding Code Duplication

One concern that is sometimes (and legitimately) raised about the
'declarations in headers, definitions in source' mechanism described
here is that there are two files to be kept synchronized — the header
and the source. This is usually followed up with an observation that a
macro can be used so that the header serves double duty — normally
declaring the variables, but when a specific macro is set before the
header is included, it defines the variables instead.

Another concern can be that the variables need to be defined in each of
a number of 'main programs'. This is normally a spurious concern; you
can simply introduce a C source file to define the variables and link
the object file produced with each of the programs.

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

file3a.h

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

Variable initialization

The problem with this scheme as shown is that it does not provide for
initialization of the global variable. With C99 or C11 and variable argument
lists for macros, you could define a macro to support initialization too.
(With C89 and no support for variable argument lists in macros, there is no
easy way to handle arbitrarily long initializers.)

file3b.h

#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

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.

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


The next two files complete the source for 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.

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:

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

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.

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

Multiple inclusion with variable definitions

However, it can be done subject to a not too unreasonable constraint.
Let's introduce a new set of file names:

  • external.h for the EXTERN macro definitions, etc.

  • file1c.h to define types (notably, struct oddball, the type of oddball_struct).

  • file2c.h to define or declare the global variables.

  • file3c.c which defines the global variables.

  • file4c.c which simply uses the global variables.

  • file5c.c which shows that you can declare and then define the global variables.

  • file6c.c which shows that you can define and then (attempt to) declare the global variables.

In these examples, file5c.c 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.

The restrictions for this to work are:

  1. The header defining or declaring the global variables may not itself
    define any types.

  2. Immediately before you include a header that should define variables,
    you define the macro DEFINE_VARIABLES.

  3. The header defining or declaring the variables has stylized contents.

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

  • prog6 uses 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.


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

You can partially work around the problem by revising file2c.h into
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:

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

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

  • prog9 uses 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.

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

孤凫 2024-08-12 11:08:55

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

An extern variable is a declaration (thanks to sbi for the correction) of a variable which is defined in another translation unit. That means the storage for the variable is allocated in another file.

Say you have two .c-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.

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
秋日私语 2024-08-12 11:08:55

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

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

如果您不将其声明为 extern,您将得到两个名称相同但完全不相关的变量,以及该变量的多个定义的错误。

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

So you can decide to use a variable in a translation unit and then access it from another one, then in the second one you declare it as extern and the symbol will be resolved by the linker.

If you don't declare it as extern you'll get 2 variables named the same but not related at all, and an error of multiple definitions of the variable.

幸福丶如此 2024-08-12 11:08:55
                 declare | define   | initialize |
                ----------------------------------

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

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

                 declare | define   | initialize |
                ----------------------------------

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

Declaration won't allocate memory (the variable must be defined for memory allocation) but the definition will.
This is just another simple view on the extern keyword since the other answers are really great.

蓝海似她心 2024-08-12 11:08:55

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

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

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

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

When encountering an extern, the compiler can only find out its type, not where it "lives", so it can't resolve the reference.

You are telling it, "Trust me. At link time this reference will be resolvable."

人间不值得 2024-08-12 11:08:55

添加 extern 将变量定义转变为变量声明。请参阅此线程了解声明和定义之间的区别。

Adding an extern turns a variable definition into a variable declaration. See this thread as to what's the difference between a declaration and a definition.

娇纵 2024-08-12 11:08:55

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

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

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

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

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

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

多情出卖 2024-08-12 11:08:55

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

The correct interpretation of extern is that you tell something to the compiler. You tell the compiler that, despite not being present right now, the variable declared will somehow be found by the linker (typically in another object (file)). The linker will then be the lucky guy to find everything and put it together, whether you had some extern declarations or not.

眉目亦如画i 2024-08-12 11:08:55

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更新ELF规范“符号表”章节解释:

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

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

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

在 GCC 4.8 上测试。

C++17 内联变量

在 C++17 中,您可能希望使用内联变量而不是外部变量,因为它们使用简单(只需在标头中定义一次)并且功能更强大(支持 constexpr)。请参阅:“const static”是什么意思C 和 C++?

GCC ELF Linux implementation

Other answers have covered the language usage side of view, so now let's have a look at how it is implemented in this implementation.

main.c

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

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.

From now on, it is the job of the linker to make the final program, but the extern information has already been extracted from the source code into the object file.

Tested on GCC 4.8.

C++17 inline variables

In C++17, you might want to use inline variables instead of extern ones, as they are simple to use (can be defined just once on header) and more powerful (support constexpr). See: What does 'const static' mean in C and C++?

塔塔猫 2024-08-12 11:08:55

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

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

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

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

千纸鹤 2024-08-12 11:08:55

在 C 语言中,文件 example.c 中的变量被赋予局部作用域。编译器期望变量在同一个文件 example.c 中具有其定义,当它没有找到相同的变量时,它将抛出一个错误。另一方面,函数默认具有全局作用域。因此,您不必明确向编译器提及“看哥们……您可能会在这里找到此函数的定义”。对于包含其声明的文件的函数就足够了。(您实际上称为头文件的文件)。
例如,考虑以下 2 个文件:
example.c

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

example1.c

int a = 5;

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

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

您将得到以下输出: The value of a is <5>

In C a variable inside a file say example.c is given local scope. The compiler expects that the variable would have its definition inside the same file example.c and when it does not find the same , it would throw an error.A function on the other hand has by default global scope . Thus you do not have to explicitly mention to the compiler "look dude...you might find the definition of this function here". For a function including the file which contains its declaration is enough.(The file which you actually call a header file).
For example consider the following 2 files :
example.c

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

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

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

月下客 2024-08-12 11:08:55

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

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

extern
allows one module of your program to access a global variable or function declared in another module of your program.
You usually have extern variables declared in header files.

If you don't want a program to access your variables or functions, you use static which tells the compiler that this variable or function cannot be used outside of this module.

长发绾君心 2024-08-12 11:08:55

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

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

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

extern is used to let other C files or external components know this variable is already defined somewhere. Example: if you are building a library, no need to define global variable mandatorily somewhere in library itself. The library will be compiled directly, but while linking the file, it checks for the definition.

倒带 2024-08-12 11:08:55

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

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

苏璃陌 2024-08-12 11:08:55

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

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

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

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

不顾 2024-08-12 11:08:55

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

大约 15 年前,这个问题在一个微芯片论坛上得到了优雅的解决
/* 请参阅“http:www.htsoft.com”/
/
“forum/all/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
#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 作为不同的类型。相关问题
也就是说,如果您说在另一个文件中将testing_mode声明为int
它会认为它是一个 16 位 var 并覆盖 ram 的其他部分,可能会破坏另一个变量。调试困难!

With xc8 you have to be careful about declaring a variable
as the same type in each file as you could , erroneously,
declare something an int in one file and a char say in another.
This could lead to corruption of variables.

This problem was elegantly solved in a microchip forum some 15 years ago
/* See "http:www.htsoft.com" /
/
"forum/all/showflat.php/Cat/0/Number/18766/an/0/page/0#18766"

But this link seems to no longer work...

So I;ll quickly try to explain it;
make a file called global.h.

In it declare the following

#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

Now in the file main.c

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

This means in main.c the variable will be declared as an unsigned char.

Now in other files simply including global.h will
have it declared as an extern for that file.

extern unsigned char testing_mode;

But it will be correctly declared as an unsigned char.

The old forum post probably explained this a bit more clearly.
But this is a real potential gotcha when using a compiler
that allows you to declare a variable in one file and then declare it extern as a different type in another. The problems associated with
that are if you say declared testing_mode as an int in another file
it would think it was a 16 bit var and overwrite some other part of ram, potentially corrupting another variable. Difficult to debug!

戒ㄋ 2024-08-12 11:08:55

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

In short extern means that variable is defined in other module and its address will be known at link time. The compiler does not reserve memory in current module and knows the variable type. To understand extern is good to have at least little experience with assembler.

回梦 2024-08-12 11:08:55

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

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

extern keyword before a symbol (a var or function) tells the linker that it(the source file) uses an external symbol. This can be seen by running nm -a on such an object file (.o) which uses or assigns a value to a extern var (remember to declare a extern symbol on top like this extern int x or still better, use a header file with extern before vars and functions can be without extern; then in main assign a value to it like this x=5;), i find undefined bss info (letter B written) against such an extern var(symbol). This means x is still unresolved and will be resolved when ld is run (during link-time).

why always use extern in headers?
If i don't use extern, just declare int x, the declaration becomes sort-of strong and without extern, and this redifines the same variable in every source that includes the header, effectively shadowing the original variable. therefore with just int x in a.h header, I redefine a new global variable x in every source that include this a.h. This var in the source, this without-extern var decl in headers shadows(it doesn't shadow exactly, it's redifining a global variable x in every source code that includes the header with just int x, without extern, when i include such header and try to compile .o from such files, every .o has its own definition of this global variable x which was included in the header without extern, and at the time of linking, I get the error multiple definition of variable or symbol x) an important variable defined somewhere of somewhere else in the source files.
Important! it is necessary to use extern before vars in headers.
Functions are already extern by-default.

黄昏下泛黄的笔记 2024-08-12 11:08:55

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

我在多个文件中使用这种模式。因此,为了使事情尽可能独立,我只是重用单个 GLOBAL & 。每个标头中的 GLOBALINIT 宏。我的标题看起来像这样:

//file foo_globals.h
#pragma once  
#include "foo.h"  //contains Foo typedef
#include <atomic>

#ifdef GLOBAL  
#undef GLOBAL  
#endif  

#ifdef GLOBALINIT
#undef GLOBALINIT
#endif

#ifdef GLOBAL_FOO_IMPLEMENTATION  
#define GLOBAL  
#define GLOBALINIT(x) = x
#else  
#define GLOBAL extern  
#define GLOBALINIT(x)
#endif  

GLOBAL Foo foo1 GLOBALINIT({2, 3, 4})
GLOBAL std::atomic_bool flag1 GLOBALINIT(true);
GLOBAL std::atomic_uint counter1 GLOBALINIT(5);


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

//file uses_extern_foo.cpp
#include "foo_globals.h

A very short solution I use to allow a header file to contain the extern reference or actual implementation of an object. The file that actually contains the object just does #define GLOBAL_FOO_IMPLEMENTATION. Then when I add a new object to this file it shows up in that file also without me having to copy and paste the definition.

I use this pattern across multiple files. So in order to keep things as self contained as possible, I just reuse the single GLOBAL & GLOBALINIT macros in each header. My headers look like this:

//file foo_globals.h
#pragma once  
#include "foo.h"  //contains Foo typedef
#include <atomic>

#ifdef GLOBAL  
#undef GLOBAL  
#endif  

#ifdef GLOBALINIT
#undef GLOBALINIT
#endif

#ifdef GLOBAL_FOO_IMPLEMENTATION  
#define GLOBAL  
#define GLOBALINIT(x) = x
#else  
#define GLOBAL extern  
#define GLOBALINIT(x)
#endif  

GLOBAL Foo foo1 GLOBALINIT({2, 3, 4})
GLOBAL std::atomic_bool flag1 GLOBALINIT(true);
GLOBAL std::atomic_uint counter1 GLOBALINIT(5);


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

//file uses_extern_foo.cpp
#include "foo_globals.h
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文