提问人: 提问时间:9/16/2009 最后编辑:user12002570 更新时间:5/17/2023 访问量:962346
如何使用 extern 在源文件之间共享变量?
How do I use extern to share variables between source files?
问:
我知道 C 中的全局变量有时有关键字。什么是变量?声明是什么样的?它的范围是什么?extern
extern
这与在源文件之间共享变量有关,但这是如何精确工作的呢?我在哪里使用?extern
答:
Extern 是用于声明变量本身驻留在另一个转换单元中的关键字。
因此,您可以决定在翻译单元中使用一个变量,然后从另一个转换单元访问它,然后在第二个转换单元中将其声明为 extern,该符号将由链接器解析。
如果你不将其声明为 extern,你将得到 2 个名称相同但完全不相关的变量,以及变量的多个定义的错误。
评论
变量是在另一个翻译单元中定义的变量的声明(感谢 sbi 的更正)。这意味着变量的存储空间在另一个文件中分配。extern
假设您有两个 -files 和 .如果在 中定义了一个全局变量,并且想要在 中访问此变量,则必须在 中使用 。.c
test1.c
test2.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
评论
extern int test1_var;
int test1_var;
extern
extern
添加 将变量定义转换为变量声明。请参阅此线程,了解声明和定义之间的区别。extern
评论
int foo
extern int foo
extern 告诉编译器相信您此变量的内存是在其他地方声明的,因此它不会尝试分配/检查内存。
因此,您可以编译一个引用了 extern 的文件,但如果该内存未在某处声明,则无法链接。
对全局变量和库很有用,但很危险,因为链接器不进行类型检查。
评论
只有在您正在构建的程序时,使用才有意义
由链接在一起的多个源文件组成,其中一些
例如,在源文件中定义的变量需要
在其他源文件中引用,如 .extern
file1.c
file2.c
当编译器被告知 变量存在(这是它的类型);它不会分配 该点变量的存储。
当编译器为 变量。
您可以多次声明一个变量(尽管一次就足够了); 在给定范围内,您只能定义一次。 变量定义也是一个声明,但不是所有变量 声明是定义。
声明和定义全局变量的最佳方式
声明和定义全局变量的干净、可靠的方法是使用
包含变量声明的头文件。extern
标头包含在定义变量的一个源文件中 以及引用该变量的所有源文件。 对于每个程序,一个源文件(并且只有一个源文件)定义 变量。 同样,一个头文件(并且只有一个头文件)应该声明 变量。 头文件至关重要;它支持交叉检查 独立的 TU(翻译单元 — 想想源文件)并确保 一致性。
虽然还有其他方法可以做到这一点,但这种方法很简单
可靠。
它由 和 证明:file3.h
file1.c
file2.c
文件3.h
extern int global_variable; /* Declaration of the variable */
文件1.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++; }
文件2.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.c
file1.c
file2.c
file3.h
prog1.h
该文件仅供 makefile。
它将与大多数版本的生产版本一起使用,因为大约在转弯时
千禧年。
它没有专门与GNU Make绑定。prog1.mk
prog1
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 — 单点事实)。
源文件从不包含变量的声明 — 源文件始终包含声明它们的 (OLE) 标头。
extern
对于任何给定的变量,只有一个源文件定义变量, 最好也初始化它。(虽然没有必要 显式初始化为零,它不会造成伤害,并且可以做一些好处, 因为一个特定的初始化定义只能有一个 程序中的全局变量)。
定义变量的源文件还包括 确保定义和声明一致。
函数永远不需要使用 声明变量。
extern
尽可能避免使用全局变量 - 请改用函数。
这个答案的源代码和文本可以在我的 SOQ(Stack Overflow 问题)中找到 GitHub 上的存储库位于 src/so-0143-3204 子目录中。
如果你不是一个有经验的 C 程序员,你可以(也许 应该)停止阅读这里。
定义全局变量的方法不太好
使用一些(实际上是许多)C 编译器,您可以摆脱 也称为变量的“通用”定义。 这里的“通用”是指 Fortran 中用于共享的一种技术 源文件之间的变量,使用(可能已命名的)COMMON 块。 这里发生的情况是,许多文件中的每一个都提供了一个暂定的 变量的定义。 只要不超过一个文件提供初始化定义, 然后,各种文件最终共享一个通用的单一定义 变量:
文件10.c
#include "prog2.h"
long l; /* Do not do this in portable code */
void inc(void) { l++; }
文件11.c
#include "prog2.h"
long l; /* Do not do this in portable code */
void dec(void) { l--; }
文件12.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 标准的字母和 “一个定义规则”——它是官方未定义的行为:
使用具有外部链接的标识符,但在程序中 标识符不存在一个外部定义,或者 标识符未使用,并且存在多个外部 标识符的定义 (6.9)。
外部定义是一个外部声明,它也是一个 函数的定义(内联定义除外)或 对象。 如果在 表达式(除了作为结果为整数常量的 OR 运算符操作数的一部分),在 整个程序应只有一个外部定义 标识符;否则,不得超过 一。161)
sizeof
_Alignof
161) 因此,如果使用外部链接声明的标识符 不在表达式中使用,不需要外部定义 它。
但是,C 标准也在信息性附录 J 中将其列为 通用扩展。
标识符可能有多个外部定义 一个对象,无论是否明确使用关键字 extern;如果 定义不一致,或者初始化了多个定义, 行为未定义 (6.9.2)。
由于并不总是支持此技术,因此最好避免 使用它,尤其是当您的代码需要可移植时。 使用这种技术,你也可能最终得到无意的类型 一语双关。
如果上面的某个文件声明为 a 而不是 a,则 C 的类型不安全链接器可能不会发现不匹配。
如果您在 64 位和 的机器上,您甚至不会
收到警告;在具有 32 位和 64 位的机器上,
您可能会收到有关不同大小的警告 - 链接器
将使用最大的大小,就像 Fortran 程序采用
所有常见块的最大尺寸。l
double
long
long
double
long
double
请注意,2020-05-07 发布的 GCC 10.1.0 更改了
默认编译选项使用 -fno-common
,这意味着
默认情况下,上面的代码不再链接,除非您覆盖
默认为 with(或使用属性等 — 请参阅链接)。-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.c
file10.c
file11.c
file12.c
prog2.h
警告
正如此处评论中所述,以及我对类似问题的回答中所述,使用多个 全局变量的定义导致未定义的行为(J.2; §6.9),这是标准表达“任何事情都可能发生”的方式。 可能发生的一件事是程序的行为与您一样 期望;J.5.11 大约说,“你可能更幸运 比你应得的”。 但是依赖于外部变量的多个定义的程序 — 无论有没有明确的 'extern' 关键字 — 都不是严格意义上的 符合程序,不能保证在任何地方都能工作。 等效地:它包含一个错误,该错误可能会或可能不会显示。
违反准则
当然,有很多方法可以打破这些准则。 有时,可能有充分的理由违反准则,但是 这样的场合是极不寻常的。
faulty_header.h
int some_var; /* Do not do this in a header!!! */
注 1:如果标头定义了没有关键字的变量,
然后,包含标头的每个文件都会创建一个暂定定义
变量。
如前所述,这通常有效,但 C 标准不行
保证它会起作用。extern
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:如果标头定义了静态变量(带或不带 initialization),则每个源文件最终都有自己的私有文件 “global”变量的版本。
例如,如果变量实际上是一个复数数组,这可能会导致 到代码的极端重复。它偶尔可以是一个 达到某种效果的明智方法,但这是非常不寻常的。
总结
使用我首先展示的标头技术。
它可靠地工作,无处不在。
特别要注意的是,声明
包含在使用它的每个文件中,包括定义它的文件。
这确保了一切都是自洽的。global_variable
在声明和定义函数时也会出现类似的问题—— 类似的规则也适用。 但问题是专门关于变量的,所以我保留了 仅对变量进行回答。
原答案结束
如果你不是一个有经验的 C 程序员,你可能应该停止阅读这里。
后期主要添加
避免代码重复
有时(并且合法地)提出的一个担忧是关于 描述了“标头中的声明,源代码中的定义”机制 这里有两个文件需要保持同步——标头 和来源。这通常伴随着一个观察结果,即 可以使用宏,以便标头具有双重作用 — 通常 声明变量,但是当在 标头,它定义变量。
另一个问题可能是,需要在每个变量中定义变量 一些“主要程序”。这通常是一个虚假的问题;你 可以简单地引入一个 C 源文件来定义变量和链接 与每个程序一起生成的对象文件。
典型的方案是这样工作的,使用原始的全局变量
插图如下:file3.h
文件3a.h
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#else
#define EXTERN extern
#endif /* DEFINE_VARIABLES */
EXTERN int global_variable;
文件1a.c
#define DEFINE_VARIABLES
#include "file3a.h" /* Variable defined - but not initialized */
#include "prog3.h"
int increment(void) { return global_variable++; }
文件2a.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 并且不支持宏中的变量参数列表,因此没有 处理任意长初始值设定项的简单方法。
文件3b.h
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#define INITIALIZER(...) = __VA_ARGS__
#else
#define EXTERN extern
#define INITIALIZER(...) /* nothing */
#endif /* DEFINE_VARIABLES */
EXTERN int global_variable INITIALIZER(37);
EXTERN struct { int a; int b; } oddball_struct INITIALIZER({ 41, 43 });
反转 #if
和 #else
块的内容,修复 Denis Kniazhev 发现的错误
文件1b.c
#define DEFINE_VARIABLES
#include "file3b.h" /* Variables now defined and initialized */
#include "prog4.h"
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
文件2b.c
#include "file3b.h"
#include "prog4.h"
#include <stdio.h>
void use_them(void)
{
printf("Global variable: %d\n", global_variable++);
oddball_struct.a += global_variable;
oddball_struct.b -= global_variable / 2;
}
显然,古怪结构的代码不是你通常想要的
写,但它说明了这一点。第二个参数的第一个参数
调用 is 和剩余参数
(本例中为单数)是 。没有 C99 或类似支持
对于宏的变量参数列表,需要
包含逗号是非常有问题的。INITIALIZER
{ 41
43 }
包含正确的头文件3b.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.c
file1b.c
file2b.c
prog4.h
file3b.h
接头护罩
任何标头都应受到保护,以防止重新包含,以便键入 定义(枚举、结构或联合类型,或通常的 typedefs)不 引起问题。标准技术是包裹身体 标头保护中的标头,例如:
#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED
...contents of header...
#endif /* FILE3B_H_INCLUDED */
标头可能会间接包含两次。例如,如果 includes 用于未显示的类型定义,
并且需要同时使用 header 和 ,然后
您有一些更棘手的问题需要解决。显然,您可能会修改
要仅包含的标题列表。但是,您可能不是
了解内部依赖关系 — 理想情况下,代码应该
继续工作。file4b.h
file3b.h
file1b.c
file4b.h
file3b.h
file4b.h
此外,它开始变得棘手,因为您可能会在包含之前包含以生成定义,但正常
标头保护 on 将阻止标头被重新包含。file4b.h
file3b.h
file3b.h
因此,您最多需要包含一次正文
声明,定义最多一次,但您可能同时需要两者
在单个翻译单元 (TU — 源文件和
它使用的标头)。file3b.h
具有变量定义的多重包含
但是,它可以在不太不合理的约束下完成。 让我们介绍一组新的文件名:
external.h
用于 EXTERN 宏定义等。file1c.h
定义类型(特别是 ,的类型)。struct oddball
oddball_struct
file2c.h
定义或声明全局变量。file3c.c
它定义了全局变量。file4c.c
它只是使用全局变量。file5c.c
这表明您可以声明然后定义全局变量。file6c.c
这表明您可以定义然后(尝试)声明全局变量。
在这些示例中,并多次直接包含标头,但这是显示
机制有效。这意味着,如果间接包含标头
两次,它也是安全的。file5c.c
file6c.c
file2c.h
这方面的限制是:
定义或声明全局变量的标头本身可能不是 定义任何类型。
在包含应定义变量的标头之前, 定义宏DEFINE_VARIABLES。
定义或声明变量的标头具有样式化的内容。
外部.h
/*
** This header must not contain header guards (like <assert.h> must not).
** Each time it is invoked, it redefines the macros EXTERN, INITIALIZE
** based on whether macro DEFINE_VARIABLES is currently defined.
*/
#undef EXTERN
#undef INITIALIZE
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#define INITIALIZE(...) = __VA_ARGS__
#else
#define EXTERN extern
#define INITIALIZE(...) /* nothing */
#endif /* DEFINE_VARIABLES */
文件1c.h
#ifndef FILE1C_H_INCLUDED
#define FILE1C_H_INCLUDED
struct oddball
{
int a;
int b;
};
extern void use_them(void);
extern int increment(void);
extern int oddball_value(void);
#endif /* FILE1C_H_INCLUDED */
文件2c.h
/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE2C_H_DEFINITIONS)
#undef FILE2C_H_INCLUDED
#endif
#ifndef FILE2C_H_INCLUDED
#define FILE2C_H_INCLUDED
#include "external.h" /* Support macros EXTERN, INITIALIZE */
#include "file1c.h" /* Type definition for struct oddball */
#if !defined(DEFINE_VARIABLES) || !defined(FILE2C_H_DEFINITIONS)
/* Global variable declarations / definitions */
EXTERN int global_variable INITIALIZE(37);
EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 });
#endif /* !DEFINE_VARIABLES || !FILE2C_H_DEFINITIONS */
/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE2C_H_DEFINITIONS
#endif /* DEFINE_VARIABLES */
#endif /* FILE2C_H_INCLUDED */
文件3c.c
#define DEFINE_VARIABLES
#include "file2c.h" /* Variables now defined and initialized */
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
文件4c.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; }
文件6c.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
使用。prog5.c
file3c.c
file4c.c
file1c.h
file2c.h
external.h
prog6
使用。prog5.c
file5c.c
file4c.c
file1c.h
file2c.h
external.h
prog7
使用。prog5.c
file6c.c
file4c.c
file1c.h
file2c.h
external.h
该方案避免了大多数问题。只有在以下情况下,您才会遇到问题
定义变量(如 )的标头包含在
另一个定义变量的标头(比如 )。没有
除了“不要这样做”之外,还有其他简单的方法。file2c.h
file7c.h
您可以通过修改为以下部分来解决此问题:file2c.h
file2d.h
文件2d.h
/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE2D_H_DEFINITIONS)
#undef FILE2D_H_INCLUDED
#endif
#ifndef FILE2D_H_INCLUDED
#define FILE2D_H_INCLUDED
#include "external.h" /* Support macros EXTERN, INITIALIZE */
#include "file1c.h" /* Type definition for struct oddball */
#if !defined(DEFINE_VARIABLES) || !defined(FILE2D_H_DEFINITIONS)
/* Global variable declarations / definitions */
EXTERN int global_variable INITIALIZE(37);
EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 });
#endif /* !DEFINE_VARIABLES || !FILE2D_H_DEFINITIONS */
/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE2D_H_DEFINITIONS
#undef DEFINE_VARIABLES
#endif /* DEFINE_VARIABLES */
#endif /* FILE2D_H_INCLUDED */
问题变成了“标题是否应该包含?
如果从标头中省略它,并用 和 括起任何定义调用:#undef DEFINE_VARIABLES
#define
#undef
#define DEFINE_VARIABLES
#include "file2c.h"
#undef DEFINE_VARIABLES
在源代码中(因此标头永远不会更改 的值),那么您应该是干净的。这只是一个麻烦
必须记住写额外的行。另一种选择可能是:DEFINE_VARIABLES
#define HEADER_DEFINING_VARIABLES "file2c.h"
#include "externdef.h"
externdef.h
/*
** This header must not contain header guards (like <assert.h> must not).
** Each time it is included, the macro HEADER_DEFINING_VARIABLES should
** be defined with the name (in quotes - or possibly angle brackets) of
** the header to be included that defines variables when the macro
** DEFINE_VARIABLES is defined. See also: external.h (which uses
** DEFINE_VARIABLES and defines macros EXTERN and INITIALIZE
** appropriately).
**
** #define HEADER_DEFINING_VARIABLES "file2c.h"
** #include "externdef.h"
*/
#if defined(HEADER_DEFINING_VARIABLES)
#define DEFINE_VARIABLES
#include HEADER_DEFINING_VARIABLES
#undef DEFINE_VARIABLES
#undef HEADER_DEFINING_VARIABLES
#endif /* HEADER_DEFINING_VARIABLES */
这有点复杂,但似乎是安全的(使用 ,中没有 )。file2d.h
#undef DEFINE_VARIABLES
file2d.h
文件7c.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; }
文件8c.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 */
文件8c.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;
}
文件9c.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.c
file7c.c
file9c.c
prog9
使用。prog8.c
file8c.c
file9c.c
然而,这些问题在实践中相对不太可能发生, 特别是如果您将标准建议用于
避免全局变量
这个展览有什么遗漏吗?
_忏悔_:这里概述的“避免重复代码”方案是 开发是因为该问题影响了我处理的一些代码(但不拥有), 并且是第一部分概述的方案的一个微不足道的问题 答案是。但是,原始方案只剩下两个 要修改以保留变量定义和声明的位置 同步,这比拥有外部变量向前迈出了一大步 分散在整个代码库中的声明(这确实很重要 当总共有数千个文件时)。但是,代码 名为“fileNc.[ch]'(加上 'external.h' 和 'externdef.h') 表明它可以工作。显然,这并不难 创建一个标题生成器脚本,为您提供标准化模板 用于定义和声明头文件的变量。铌这些是玩具程序,只有勉强足够的代码来制作它们
有点有趣。例子中有重复
可以删除,但不是为了简化教学解释。
(例如:和的区别是名称
包含的标头之一。有可能
重新组织代码,使函数不重复,但
它隐瞒的比揭示的要多。prog5.c
prog8.c
main()
评论
foo.h
#define FOO_INITIALIZER { 1, 2, 3, 4, 5 }
enum { FOO_SIZE = sizeof((int [])FOO_INITIALIZER) / sizeof(((int [])FOO_INITIALIZER)[0]) };
extern int foo[];
int foo[FOO_SIZE] = FOO_INITIALIZER;
FOO_SIZE
我喜欢把 extern 变量看作是你对编译器的承诺。
当遇到 extern 时,编译器只能找出它的类型,而不能找出它“存在”的位置,因此它无法解析引用。
你告诉它,“相信我。在链接时,此引用将是可解析的。
评论
对 extern 的正确解释是,你告诉编译器一些事情。你告诉编译器,尽管现在不存在,但声明的变量将以某种方式被链接器找到(通常在另一个对象(文件)中)。然后,链接者将是幸运的人,可以找到所有内容并将其放在一起,无论您是否有一些外部声明。
在 C 语言中,文件中的一个变量(例如 example.c)被赋予了局部作用域。编译器期望变量在同一个文件 example.c 中定义,当它没有找到相同的定义时,它会抛出错误。另一方面,函数默认具有全局范围。因此,您不必明确地向编译器提及“看,伙计......你可以在这里找到这个函数的定义”。对于包含包含其声明的文件的函数就足够了。(您实际称为头文件的文件)。
例如,请考虑以下 2 个文件:
example.c
#include<stdio.h>
extern int a;
main(){
printf("The value of a is <%d>\n",a);
}
例子1.c
int a = 5;
现在,当您将这两个文件编译在一起时,请使用以下命令:
步骤 1)cc -o ex example.c example1.c 步骤 2)./ex
你得到以下输出:a 的值为 <5>
首先,关键字不用于定义变量;相反,它用于声明变量。我可以说是一个存储类,而不是一个数据类型。extern
extern
extern
用于让其他 C 文件或外部组件知道此变量已在某处定义。示例:如果您正在构建一个库,则无需在库本身的某个位置强制定义全局变量。该库将直接编译,但在链接文件时,它会检查定义。
extern 关键字与变量一起使用,以将其标识为全局变量。
它还表示您可以使用使用 extern 声明的变量 关键字,尽管它在其他文件中声明/定义。
extern
允许程序的一个模块访问在程序的另一个模块中声明的全局变量或函数。
通常在头文件中声明了 extern 变量。
如果你不希望程序访问你的变量或函数,你可以使用它告诉编译器这个变量或函数不能在这个模块之外使用。static
extern
,以便一个文件可以完全访问另一个文件中的全局参数。first.c
second.c
可以在文件或任何头文件 include 中声明。extern
first.c
first.c
评论
extern
first.c
second.c
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)。请参阅:C 和 C++ 中的“常量静态”是什么意思?
评论
readelf
nm
extern
notExtern
notExtern
extern
extern int notExtern;
notExtern
global_def
extern_ref
int extern_ref = 57;
extern int global_def;
extern
简单地说,一个变量是在其他地方(例如,在另一个文件中)定义的。
使用 xc8 时,您必须小心声明变量
作为每个文件中的相同类型,错误地,
在一个文件中声明一些东西,在另一个文件中声明一个发言权。
这可能会导致变量损坏。int
char
大约15年前,这个问题在微芯片论坛上得到了很好的解决 /* 参见 “http:www.htsoft.com” / / “论坛/全部/showflat.php/Cat/0/Number/18766/an/0/page/0#18766”
但是这个链接似乎不再起作用......
所以我会很快尝试解释它; 创建一个名为 global.h 的文件。
在其中声明以下内容
#ifdef MAIN_C
#define GLOBAL
/* #warning COMPILING MAIN.C */
#else
#define GLOBAL extern
#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
旧的论坛帖子可能更清楚地解释了这一点。
但是,在使用编译器时,这是一个真正的潜力
这允许您在一个文件中声明一个变量,然后在另一个文件中将其声明为不同的类型。与以下问题相关的问题
也就是说,如果您说在另一个文件中将 testing_mode 声明为 int
它会认为它是一个 16 位 var 并覆盖 RAM 的其他部分,可能会损坏另一个变量。调试难!gotcha
declare | define | initialize |
----------------------------------
extern int a; yes no no
-------------
int a = 2019; yes yes yes
-------------
int a; yes yes no
-------------
声明不会分配内存(必须为内存分配定义变量),但定义会。 这只是关于 extern 关键字的另一个简单视图,因为其他答案真的很棒。
我用一个非常简短的解决方案来允许头文件包含对象的外部引用或实际实现。实际包含该对象的文件只是 .然后,当我向此文件添加新对象时,它也会显示在该文件中,而无需复制和粘贴定义。#define GLOBAL_FOO_IMPLEMENTATION
我在多个文件中使用此模式。因此,为了尽可能保持独立,我只是在每个标头中重用单个 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
评论
简而言之,意味着变量在其他模块中定义,其地址将在链接时已知。编译器不会在当前模块中保留内存,并且知道变量类型。至少对汇编程序有一点经验是很好的。extern
extern
符号(var 或函数)之前的 extern 关键字告诉链接器它(源文件)使用外部符号。这可以通过在这样的目标文件(.o)上运行nm -a来看出,该文件使用或为extern var赋值(记住在上面声明一个extern符号,如这个extern int x,或者更好的是,在vars之前使用带有extern的头文件,函数可以没有extern;然后在main中为它赋值,如x=5;), 我发现针对这样的外部 var(符号)的未定义的 bss 信息(字母 B 写)。这意味着 x 仍未解析,将在运行 ld 时(在链接时)解析。
为什么总是在标头中使用 extern? 如果我不使用 extern,只需声明 int x,声明就会变得很强且没有 extern,这会在包含标头的每个源中重新定义相同的变量,从而有效地掩盖原始变量。因此,在 a.h 标头中仅使用 int x,我在包含此 a.h 的每个源中重新定义了一个新的全局变量 x。源代码中的这个 var,标头阴影中的这个 without-extern var decl(它并不完全是阴影,它在每个源代码中重新定义一个全局变量 x,其中包含仅包含带有 int x 的标头,没有 extern,当我包含此类标头并尝试从此类文件中编译 .o 时,每个 .o 都有自己的全局变量 x 定义,该变量包含在没有 extern 的标头中, 在链接时,我收到错误变量或符号 x) 的多重定义,这是源文件中其他地方某处定义的重要变量。 重要!有必要在标头中的 var 之前使用 extern。 默认情况下,函数已经是 extern 的。
评论