如何使用extern在源文件之间共享变量?
我知道C中的全局变量有时会有extern
关键字。 什么是extern
变量? 什么是宣言? 它的范围是什么?
这与在源文件中共享变量有关,但它是如何精确地工作的? 我在哪里使用extern
?
使用extern
,只有当您正在构建的程序由多个源文件链接在一起时才相关,其中一些定义的变量(例如,源文件file1.c
需要在其他源文件(如file2.c
引用) file2.c
。
理解定义变量和声明变量之间的区别非常重要:
你可以多次声明变量(尽管一次就足够了); 您只能在给定范围内定义一次。 变量定义也是一个声明,但并非所有的变量声明都是定义。
声明和定义全局变量的最佳方法
尽管还有其他的方法,但声明和定义全局变量的干净可靠的方法是使用头文件file3.h
来包含变量的extern
声明。 标题包含在一个定义变量的源文件中,并由所有引用该变量的源文件包含。 对于每个程序,一个源文件(并且只有一个源文件)定义该变量。 同样,一个头文件(并且只有一个头文件)应该声明该变量。
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: %dn", global_variable++);
}
这是使用它们的最佳方式。
接下来的两个文件完成prog1
的源代码:
请注意,我在标题中的函数声明前面使用关键字extern
(例如,在prog1.h中所示),以匹配标题中变量声明前面的extern
。 很多人不喜欢在功能前面使用extern
, 编译器不关心 - 最终,只要你一致,我也不会。
prog1.h
extern void use_it(void); // "extern" is optional here; see note above
extern int increment(void); // "extern" is optional here; see note above
prog1.c的
#include "file3.h"
#include "prog1.h"
#include <stdio.h>
int main(void)
{
use_it();
global_variable += 19;
use_it();
printf("Increment: %dn", increment());
return 0;
}
prog1
使用prog1.c
, file1.c
, file2.c
, file3.h
和prog1.h
。 方针
规则只能由专家打破,只有很好的理由:
extern
声明 - 永远不会有static
或非限定变量定义。 extern
声明 - 源文件始终包含声明它们的(唯一)标题。 extern
声明一个变量。 如果你不是一个有经验的C程序员,你可以(也许应该)在这里停止阅读。
这个答案的源代码和文本在src / so-0143-3204子目录中的GitHub的SOQ(Stack Overflow Questions)存储库中可用。
不是很好的定义全局变量的方法
使用一些(实际上是很多)C编译器,你也可以避开所谓的变量的'通用'定义。 'Common'是指Fortran中用于在源文件之间共享变量的一种技术,使用(可能命名为)COMMON块。 这里发生的是,许多文件中的每一个都提供了变量的暂定义。 只要不超过一个文件提供了初始化定义,那么各种文件最终将共享该变量的通用单一定义:
file10.c
#include "prog2.h"
int i; /* Do not do this in portable code */
void inc(void) { i++; }
file11.c
#include "prog2.h"
int i; /* Do not do this in portable code */
void dec(void) { i--; }
file12.c
#include "prog2.h"
#include <stdio.h>
int i = 9; /* Do not do this in portable code */
void put(void) { printf("i = %dn", i); }
这种技术不符合C标准的字母和“一个定义规则”,但C标准将其列为其一个定义规则的常见变体。 因为这种技术并不总是被支持的,所以最好避免使用它,特别是如果你的代码需要可移植的话。 使用这种技术,你也可以结束无意的类型双关。 如果其中一个文件声明i
为double
而不是int
,那么C的类型不安全的连接器可能不会发现该不匹配。 如果你在64位int
和double
的机器上,你甚至不会收到警告; 在具有32位int
和64位double
,您可能会收到关于不同大小的警告 - 链接器将使用最大的大小,正如Fortran程序将采用任何常见块的最大大小一样。
这在信息附录J的C标准中作为一个通用扩展提到:
J.5.11多个外部定义
对象的标识符可能有多个外部定义,有或没有明确使用关键字extern; 如果定义不一致,或者多于一个被初始化,则行为是不确定的(6.9.2)。
接下来的两个文件完成了prog2
的源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.5.11大概说,“你可能比你应得的幸运更多”。 但是一个依赖extern变量的多重定义的程序 - 无论是否使用明确的“extern”关键字 - 都不是严格符合要求的程序,并且不能保证在任何地方都能正常工作。 等同:它包含一个可能会或可能不会显示的错误。
违反准则
faulty_header.h
int some_var; /* Do not do this in a header!!! */
注1:如果标题定义了不带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:如果头文件定义了一个静态变量(有或没有初始化),那么每个源文件最后都有它自己的'全局'变量的私有版本。
例如,如果变量实际上是一个复杂的数组,那么这会导致代码的极度重复。 它可以非常偶然地是达到某种效果的明智方式,但这是非常不寻常的。
概要
使用我首先展示的标题技术。 它工作可靠,无处不在。 请特别注意,声明global_variable
的头文件包含在每个使用它的文件中 - 包括定义它的文件。 这确保了一切都是自洽的。
声明和定义功能也有类似的问题 - 类似的规则适用。 但问题特别是关于变量,所以我只保留了变量的答案。
(完整的程序使用函数,因此函数声明已悄悄进入。我在头文件中的函数声明前使用关键字extern
来匹配头文件中变量声明前面的extern
。许多人不希望在函数前面使用extern
,编译器不关心 - 最终,只要你一致,我也不会。)
原答复结束
如果你不是一个有经验的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: %dn", global_variable++);
}
接下来的两个文件完成prog3
的源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: %dn", 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
块的内容,修复由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: %dn", global_variable++);
oddball_struct.a += global_variable;
oddball_struct.b -= global_variable / 2;
}
显然,古怪的结构的代码不是你通常写的,但它说明了这一点。 第二次调用INITIALIZER
的第一个参数是{ 41
,剩下的参数(在这个例子中是单数)是43 }
。 如果没有C99或对宏的可变参数列表的类似支持,则需要包含逗号的初始化程序非常成问题。
每个Denis Kniazhev file3b.h
包含正确的头文件file3b.h
(而不是fileba.h
)
接下来的两个文件完成了prog4
的源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: %dn", increment());
printf("Oddball: %dn", oddball_value());
return 0;
}
prog4
使用prog4.c
, file1b.c
, file2b.c
, prog4.h
, file3b.h
。 标头卫兵
任何头文件都应该受到保护以防止重新加入,所以类型定义(enum,struct或union类型或typedef通常)不会导致问题。 标准技术是将头部的主体包裹在头部守卫中,例如:
#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
。 但是,您可能没有意识到内部依赖关系 - 并且代码应该在理想情况下继续工作。
此外,它开始变得棘手,因为在包含file3b.h
以生成定义之前,您可能会包含file4b.h
,但file3b.h
上的正常头防护将防止头被重新包含。
因此,您最多需要包含file3b.h
的主体一次用于声明,最多一次用于定义,但您可能需要同时使用单个翻译单元(TU - 源文件和它使用的标头的组合) 。
多变量定义包含
但是,这可以通过一个不太无理的限制来完成。 我们来介绍一组新的文件名:
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
几次,但这是显示机制起作用的最简单的方法。 这意味着如果标题间接包含两次,它也是安全的。
这项工作的限制是:
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: %dn", 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
, prog6
和prog7
的源代码(提供了一个主程序):
prog5.c
#include "file2c.h"
#include <stdio.h>
int main(void)
{
use_them();
global_variable += 19;
use_them();
printf("Increment: %dn", increment());
printf("Oddball: %dn", 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
来部分解决该问题:
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
,在file2d.h
没有#undef DEFINE_VARIABLES
file2d.h
)。
file7c.c
/* Declare variables */
#include "file2d.h"
/* Define variables */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"
/* Declare variables - again */
#include "file2d.h"
/* Define variables - again */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
file8c.h
/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE8C_H_DEFINITIONS)
#undef FILE8C_H_INCLUDED
#endif
#ifndef FILE8C_H_INCLUDED
#define FILE8C_H_INCLUDED
#include "external.h" /* Support macros EXTERN, INITIALIZE */
#include "file2d.h" /* struct oddball */
#if !defined(DEFINE_VARIABLES) || !defined(FILE8C_H_DEFINITIONS)
/* Global variable declarations / definitions */
EXTERN struct oddball another INITIALIZE({ 14, 34 });
#endif /* !DEFINE_VARIABLES || !FILE8C_H_DEFINITIONS */
/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE8C_H_DEFINITIONS
#endif /* DEFINE_VARIABLES */
#endif /* FILE8C_H_INCLUDED */
file8c.c
/* Define variables */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"
/* Define variables */
#define HEADER_DEFINING_VARIABLES "file8c.h"
#include "externdef.h"
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
接下来的两个文件完成了prog8
和prog9
的源代码:
prog8.c
#include "file2d.h"
#include <stdio.h>
int main(void)
{
use_them();
global_variable += 19;
use_them();
printf("Increment: %dn", increment());
printf("Oddball: %dn", oddball_value());
return 0;
}
file9c.c
#include "file2d.h"
#include <stdio.h>
void use_them(void)
{
printf("Global variable: %dn", 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()
函数,但它会隐藏更多比它显示的要多。)
一个extern
变量是一个在另一个翻译单元中定义的变量的声明(由于sbi的更正)。 这意味着变量的存储被分配在另一个文件中。
假设你有两个.c
-files 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 = %dn", test1_var);
return 0;
}
$ gcc test1.c test2.c -o test
$ ./test
test1_var = 5
Extern是用于声明变量本身驻留在另一个翻译单元中的关键字。
因此,您可以决定在翻译单元中使用变量,然后从另一个变量单元访问变量,然后在第二个变量单元中声明它为extern,并且符号将由链接程序解析。
如果你不把它声明为extern,你会得到2个变量,它们的名字相同但是完全不相关,并且有多个变量定义的错误。
链接地址: http://www.djcxy.com/p/43371.html上一篇: How do I use extern to share variables between source files?