我知道C中的全局变量有时有extern关键字。什么是extern变量?申报是什么样的?它的范围是什么?
这与跨源文件共享变量有关,但这究竟是如何工作的呢?我在哪里使用extern?
我知道C中的全局变量有时有extern关键字。什么是extern变量?申报是什么样的?它的范围是什么?
这与跨源文件共享变量有关,但这究竟是如何工作的呢?我在哪里使用extern?
Extern是用于声明变量本身驻留在另一个翻译单元中的关键字。
因此,您可以决定在一个转换单元中使用一个变量,然后从另一个转换单元访问它,然后在第二个转换单元中将其声明为extern,符号将由链接器解析。
如果不将其声明为extern,则会得到两个名称相同但完全不相关的变量,并且变量的多个定义存在错误。
extern变量是在另一个翻译单元中定义的变量的声明(感谢sbi的更正)。这意味着变量的存储空间被分配到另一个文件中。
假设您有两个.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告诉编译器信任你,这个变量的内存在其他地方声明了,所以它不会尝试分配/检查内存。
因此,您可以编译一个引用extern的文件,但是如果没有在某个地方声明该内存,则不能进行链接。
对于全局变量和库很有用,但是危险,因为链接器不进行类型检查。
使用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()函数不再重复,但是 它所隐藏的东西比它所揭示的要多。)
我喜欢把extern变量看作是你对编译器做出的承诺。
当遇到一个extern时,编译器只能找出它的类型,而不能找出它“居住”的位置,因此它不能解析引用。
你告诉它:“相信我。在链接时,这个引用将是可解析的。”
extern的正确解释是,你告诉编译器一些东西。你告诉编译器,尽管现在不存在,但声明的变量将以某种方式被链接器找到(通常在另一个对象(文件)中)。链接器将幸运地找到所有内容并将其组合在一起,无论是否有一些外部声明。
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;
现在,当你一起编译这两个文件时,使用以下命令:
步骤1)cc -o ex example.c 步骤2)。/交货
输出结果如下:a的值<5>
首先,extern关键字不用于定义变量;相反,它用于声明变量。我可以说extern是一个存储类,而不是数据类型。
extern用于让其他C文件或外部组件知道这个变量已经在某处定义。例如:如果你正在构建一个库,不需要在库本身的某个地方强制定义全局变量。库将直接编译,但在链接文件时,它将检查定义。
走读生 允许程序的一个模块访问程序的另一个模块中声明的全局变量或函数。 通常在头文件中声明extern变量。
如果你不想让一个程序访问你的变量或函数,你可以使用static,它告诉编译器这个变量或函数不能在这个模块之外使用。
Extern的使用使得一个first.c文件可以完全访问另一个secondc文件中的全局参数。
extern可以在first.c文件中声明,也可以在first.c包含的任何头文件中声明。
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++中是什么意思?
对于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的其他部分,可能会破坏另一个变量。很难调试!
declare | define | initialize |
----------------------------------
extern int a; yes no no
-------------
int a = 2019; yes yes yes
-------------
int a; yes yes no
-------------
声明不会分配内存(必须为分配内存而定义变量),但定义会。 这只是对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
简而言之,extern意味着变量是在其他模块中定义的,它的地址在链接时是已知的。编译器不会在当前模块中保留内存,并且知道变量类型。要理解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.