我知道C中的全局变量有时有extern关键字。什么是extern变量?申报是什么样的?它的范围是什么?
这与跨源文件共享变量有关,但这究竟是如何工作的呢?我在哪里使用extern?
我知道C中的全局变量有时有extern关键字。什么是extern变量?申报是什么样的?它的范围是什么?
这与跨源文件共享变量有关,但这究竟是如何工作的呢?我在哪里使用extern?
当前回答
我使用了一个非常简短的解决方案,允许头文件包含对象的extern引用或实际实现。实际包含该对象的文件只是#define GLOBAL_FOO_IMPLEMENTATION。然后,当我向这个文件添加一个新对象时,它也会显示在那个文件中,而不需要我复制和粘贴定义。
我在多个文件中使用这种模式。因此,为了保持内容尽可能自包含,我只是在每个头文件中重用单个GLOBAL宏。我的标题是这样的:
//file foo_globals.h
#pragma once
#include "foo.h" //contains definition of foo
#ifdef GLOBAL
#undef GLOBAL
#endif
#ifdef GLOBAL_FOO_IMPLEMENTATION
#define GLOBAL
#else
#define GLOBAL extern
#endif
GLOBAL Foo foo1;
GLOBAL Foo foo2;
//file main.cpp
#define GLOBAL_FOO_IMPLEMENTATION
#include "foo_globals.h"
//file uses_extern_foo.cpp
#include "foo_globals.h
其他回答
对于xc8,必须小心声明变量 在每个文件中都是相同的类型, 在一个文件中声明int型,在另一个文件中声明char型。 这可能会导致变量的破坏。
这个问题在大约15年前的一个微芯片论坛上得到了优雅的解决 /*参见“http:www.htsoft.com”/ /“论坛/ / showflat.php /猫/ 0 /数字/ 18766 / / /页面/ 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。
extern unsigned char testing_mode;
但是它将被正确地声明为unsigned char。
旧的论坛帖子可能解释得更清楚一点。 但在使用编译器时,这是一个真正的潜在问题 这允许您在一个文件中声明一个变量,然后在另一个文件中将其声明为不同的类型。这些问题与 也就是说,如果你在另一个文件中将testing_mode声明为int类型 它会认为它是一个16位的变量,并覆盖ram的其他部分,可能会破坏另一个变量。很难调试!
GCC ELF Linux实现
其他答案已经涵盖了语言使用方面的观点,所以现在让我们看看它是如何在这个实现中实现的。
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规范“符号表”章节解释:
这个节表索引表示该符号未定义。当链接编辑器将这个目标文件与另一个定义指定符号的文件结合在一起时,这个文件对符号的引用将被链接到实际的定义。
这基本上是C标准赋予extern变量的行为。
从现在开始,制作最终程序是链接器的工作,但是extern信息已经从源代码中提取到目标文件中。
在GCC 4.8上测试。
c++ 17内联变量
在c++ 17中,你可能想要使用内联变量而不是extern变量,因为它们使用简单(可以在头文件中定义一次)并且更强大(支持constexpr)。参见:“const static”在C和c++中是什么意思?
使用extern只与您正在构建的程序相关 由多个源文件链接在一起,其中的一些 例如,在源文件file1.c中定义的变量 在其他源文件中引用,例如file2.c。
重要的是要理解定义 变量,并声明 变量:
当编译器被告知变量 变量存在(这是它的类型);它不分配 变量的存储。 变量是在编译器为其分配存储时定义的 变量。
你可以多次声明一个变量(尽管一次就足够了); 在给定范围内只能定义一次。 变量定义也是一种声明,但不全是变量 声明是定义。
声明和定义全局变量的最佳方法
声明和定义全局变量的干净、可靠的方法是使用 包含变量extern声明的头文件。
头文件包含在定义变量的一个源文件中 以及所有引用该变量的源文件。 对于每个程序,都有一个源文件(且只有一个源文件)定义 变量。 类似地,一个头文件(且只有一个头文件)应该声明 变量。 头文件至关重要;它支持之间的交叉检查 独立的tu(翻译单元——考虑源文件)和保证 一致性。
虽然有其他的方法来做,这个方法是简单的和 可靠的。 它由file3.h, file1.c和file2.c演示:
file3.h
extern int global_variable; /* Declaration of the variable */
file1.c
#include "file3.h" /* Declaration made available here */
#include "prog1.h" /* Function declarations */
/* Variable defined here */
int global_variable = 37; /* Definition checked against declaration */
int increment(void) { return global_variable++; }
file2.c
#include "file3.h"
#include "prog1.h"
#include <stdio.h>
void use_it(void)
{
printf("Global variable: %d\n", global_variable++);
}
这是声明和定义全局变量的最佳方式。
接下来的两个文件完成了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使用Prog1 .c, file1.c, file2.c, file3.h和Prog1 .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}
的指导方针
只有专家才能打破规则,而且必须有充分的理由:
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.
这个答案的源代码和文本可在我的 SOQ(堆栈溢出问题) 在GitHub上的 src / - 0143 - 3204 子目录。
如果你不是一个有经验的C程序员,你可以(也许) 应该)在这里停止阅读。
这不是定义全局变量的好方法
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); }
这种技术不符合C的字母标准和 “one definition rule”——官方定义的行为:
J.2未定义的行为
使用带有外部链接的标识符,但在程序中 标识符不存在确切的外部定义,还是 该标识符未被使用,且存在多个外部标识符 标识符的定义(6.9)。
§6.9外部定义¶
外部定义是一种外部声明,也是 函数的定义(内联定义除外)或 对象。 类中使用带有外部链接声明的标识符 表达式(不作为sizeof或的操作数的一部分) _Alignof运算符,其结果是一个整数常量),在 整个程序应该有一个确切的外部定义 标识符;否则,就不会超过 one.161)
因此,如果标识符声明为外部链接 在表达式中不使用,不需要外部定义 它。
然而,C标准也在附录J中列出了它 Common扩展。
J.5.11多个外部定义
的标识符可能有多个外部定义 一个对象,无论是否显式使用关键字extern;如果 定义不一致,或者初始化了多个定义 行为未定义(6.9.2)。
因为并不总是支持这种技术,所以最好避免使用 使用它,特别是当您的代码需要可移植时。 使用这种技术,您还可以以无意类型结束 夯实。
如果上面的一个文件将l声明为double而不是a 长,C的类型不安全的连接器可能不会发现不匹配。 如果您在64位长双位的机器上,您甚至不会 得到警告;在一台32位长双位的机器上, 你可能会得到一个关于不同大小的警告-链接器 会使用最大的大小,就像Fortran程序会使用 任何普通块的最大尺寸。
注意,在2020-05-07发布的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使用pro2 .c, file10.c, file11.c, file12.c, pro2 .h。
警告
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.
违反指南
当然,有很多方法可以打破这些准则。 偶尔,可能有一个很好的理由去打破这些指导方针,但是 这样的场合极不寻常。
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'变量的版本。
例如,如果变量实际上是一个复杂数组,这可能导致 极度重复代码。偶尔,也可能是 这是达到某种效果的明智方法,但这是非常不寻常的。
总结
使用我首先展示的标题技术。 它在任何地方都可靠地工作。 特别注意,声明global_variable的头为 包含在每个使用它的文件中——包括定义它的文件。 这确保了一切都是自洽的。
在声明和定义函数时也会出现类似的问题 类似的规则也适用。 但是这个问题是关于变量的,所以我保留了 只回答变量。
原答案结束
如果你不是一个有经验的C程序员,你可能应该停止阅读这里。
晚期主要新增
避免代码重复
有时(合理地)提出的一个担忧是 '声明在头,定义在源'机制描述 这里有两个文件需要保持同步——头文件 还有源头。这通常是跟随一个观察a 宏可以被使用,因此通常情况下,头文件有双重作用 方法之前设置特定宏时,可以声明变量 头文件被包含,它定义变量代替。
另一个需要注意的问题是变量需要在每一个中定义 一些“主要程序”。这通常是一种虚假的担忧;你 可以简单地引入一个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 .c, file1a.c, file2a.c, file3a.h, Prog3 .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块的反向内容,修复由识别的错误 丹尼斯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) 丹尼斯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 .c, file1b.c, file2b.c, Prog4 .h, file3b.h。
头警卫
任何头文件都应该被保护以防重新包含,因此 定义(enum、struct或union类型,或者通常是typedefs)则不是 导致问题。标准的方法是把身体包裹起来 头文件保护中的头文件,例如:
#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED
...contents of header...
#endif /* FILE3B_H_INCLUDED */
头文件可能间接包含两次。例如,如果 File4b.h包含未显示的类型定义的file3b.h, 然后file1b.c需要同时使用头文件file4b.h和file3b.h 你还有更棘手的问题要解决。显然,你可能会复习 头列表只包含file4b.h。然而,你可能不是 意识到内部依赖关系-理想情况下,代码应该, 继续工作。
此外,它开始变得棘手,因为您可能会包含file4b.h 之前包含file3b.h来生成定义,但是正常 file3b.h上的头保护将防止头被重新包含。
因此,您最多需要包含file3b.h的主体一次 声明,定义最多只需要一次,但可能两者都需要 在一个翻译单元中(TU -源文件和 它使用的头文件)。
变量定义的多重包含
然而,它可以在不太不合理的约束下完成。 让我们引入一组新的文件名:
external.h用于EXTERN宏定义,等等。 定义类型(特别是oddball结构体,oddball_struct的类型)。 定义或声明全局变量。 File3c.c定义全局变量。 File4c.c只使用全局变量。 File5c.c,它显示了您可以声明然后定义全局变量。 File6c.c,它显示了您可以定义然后(尝试)声明全局变量。
在这些示例中,file5c.c和file6c.c直接包含头文件 File2c.h,但这是最简单的方法来显示 工作机制。这意味着如果头文件被间接包含 两次,也是安全的。
这样做的限制是:
定义或声明全局变量的头文件可能不是它自己 定义任何类型。 在包含定义变量的头文件之前, 定义宏DEFINE_VARIABLES。 定义或声明变量的头具有风格化的内容。
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 */
文件1c.h
#ifndef FILE1C_H_INCLUDED
#define FILE1C_H_INCLUDED
struct oddball
{
int a;
int b;
};
extern void use_them(void);
extern int increment(void);
extern int oddball_value(void);
#endif /* FILE1C_H_INCLUDED */
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;
}
文件5c.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、prog6和prog7的源代码(提供了一个主程序):
prog5.c
#include "file2c.h"
#include <stdio.h>
int main(void)
{
use_them();
global_variable += 19;
use_them();
printf("Increment: %d\n", increment());
printf("Oddball: %d\n", oddball_value());
return 0;
}
Prog5使用pro5 .c, file3c.c, file4c.c, file1c.h, file2c.h, external.h。 Prog6使用pro5 .c, file5c.c, file4c.c, file1c.h, file2c.h, external.h。 Prog7使用pro5 .c, file6c.c, file4c.c, file1c.h, file2c.h, external.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.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; }
接下来的两个文件完成了prog8和prog9的源代码:
prog8.c
#include "file2d.h"
#include <stdio.h>
int main(void)
{
use_them();
global_variable += 19;
use_them();
printf("Increment: %d\n", increment());
printf("Oddball: %d\n", oddball_value());
return 0;
}
file9c c。
#include "file2d.h"
#include <stdio.h>
void use_them(void)
{
printf("Global variable: %d\n", global_variable++);
oddball_struct.a += global_variable;
oddball_struct.b -= global_variable / 2;
}
Prog8使用pro8 .c, file7c.c, file9c.c。 Prog9使用prog8.c, file8c.c, file9c.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.c和prog8.c之间的区别是名称 包含的其中一个头的。这是可能的 重新组织代码,使main()函数不再重复,但是 它所隐藏的东西比它所揭示的要多。)
declare | define | initialize |
----------------------------------
extern int a; yes no no
-------------
int a = 2019; yes yes yes
-------------
int a; yes yes no
-------------
声明不会分配内存(必须为分配内存而定义变量),但定义会。 这只是对extern关键字的另一种简单看法,因为其他答案真的很棒。
我喜欢把extern变量看作是你对编译器做出的承诺。
当遇到一个extern时,编译器只能找出它的类型,而不能找出它“居住”的位置,因此它不能解析引用。
你告诉它:“相信我。在链接时,这个引用将是可解析的。”