Answers:
使用extern
仅是相关的,当程序你正在构建由链接在一起的多个源文件,其中的一些变量的定义,例如,在源文件中file1.c
需要在其他源文件中引用,如file2.c
。
重要的是要了解定义变量和声明变量之间的区别:
当编译器被告知存在一个变量(这就是它的类型)时,就声明一个变量。此时,它不会为变量分配存储空间。
一个变量被定义时,编译器分配该变量的存储。
您可以多次声明一个变量(尽管一次就足够了);您只能在给定范围内定义一次。变量定义也是声明,但并非所有变量声明都是定义。
声明和定义全局变量的干净,可靠的方法是使用头文件包含变量的extern
声明。
标头包含在一个定义该变量的源文件中,并包含在所有引用该变量的源文件中。对于每个程序,一个源文件(只有一个源文件)定义了变量。同样,一个头文件(只有一个头文件)应声明该变量。头文件至关重要。它可以在独立的TU(翻译单元,即源文件)之间进行交叉检查,并确保一致性。
尽管还有其他方法可以执行此操作,但此方法简单可靠。它是通过论证file3.h
,file1.c
并file2.c
:
extern int global_variable; /* Declaration of the variable */
#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++; }
#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
在函数声明前使用;编译器不在乎-最终,只要您保持一致,至少在源文件中,我也不会这样做。
extern void use_it(void);
extern int increment(void);
#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
是一个makefile prog1
。make
自从大约千年之交以来,它将与大多数版本的产品一起使用。它并不专门与GNU Make绑定。
# 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 —单点真相)。
源文件从不包含extern
变量声明-源文件始终包含声明它们的(唯一)标头。
对于任何给定的变量,恰好一个源文件定义了该变量,最好也将其初始化。(尽管不需要显式地初始化为零,但它无害且可以带来一些好处,因为在程序中只能存在一个对特定全局变量的初始化定义)。
定义变量的源文件还包括标头,以确保定义和声明一致。
函数永远不需要使用来声明变量extern
。
尽可能避免使用全局变量,而应使用函数。
这个答案的源代码和文本可在GitHub上的src / so-0143-3204子目录中的SOQ(堆栈溢出问题)存储库中找到。
如果您不是经验丰富的C程序员,则可以(也许应该)在这里停止阅读。
使用某些(实际上,很多)C编译器,您也可以摆脱所谓的“通用”变量定义。这里的“公用”是指Fortran中使用(可能是命名的)COMMON块在源文件之间共享变量的技术。这里发生的是,许多文件中的每一个都提供了变量的临时定义。只要提供一个初始化的定义的文件不超过一个,那么各种文件最终都会共享一个变量的通用单个定义:
#include "prog2.h"
long l; /* Do not do this in portable code */
void inc(void) { l++; }
#include "prog2.h"
long l; /* Do not do this in portable code */
void dec(void) { l--; }
#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)。
一个外部定义为外部声明,这也是一个功能(比内联定义其他)或对象的定义。如果在表达式中使用了用外部链接声明的标识符(不是作为结果为整数常数的a
sizeof
或_Alignof
运算符的操作数的一部分),则在整个程序中的某个位置应有一个标识符的外部定义;否则,不得超过一个。161)161)因此,如果在表达式中未使用通过外部链接声明的标识符,则无需为其定义外部。
但是,C标准还在参考性附录J中将其列为通用扩展之一。
一个对象的标识符可能有多个外部定义,无论是否明确使用关键字extern都可以;如果定义不一致,或者初始化了多个定义,则行为未定义(6.9.2)。
由于并不总是支持此技术,因此最好避免使用它,尤其是在您的代码需要可移植的情况下。使用这种技术,您还可能最终会意外地进行类型调整。
如果上述文件l
中的一个声明为a double
而不是a long
,则C的类型不安全的链接程序可能不会发现不匹配。如果您使用的是64位long
和的计算机double
,那么您甚至都不会收到警告。在具有32位long
和64位的计算机上double
,您可能会收到有关大小不同的警告-链接器将使用最大大小,就像Fortran程序将占用所有常见块的最大大小一样。
请注意,GCC 10.1.0(于2020-05-07发布)将默认编译选项更改为use -fno-common
,这意味着默认情况下,除非您使用-fcommon
(或使用属性等覆盖)默认值,否则上述代码不再链接。请参阅链接)。
接下来的两个文件完成了以下内容的源代码prog2
:
extern void dec(void);
extern void put(void);
extern void inc(void);
#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变量的多个定义(带有或不带有显式的'extern'关键字)的程序并不是严格符合要求的程序,不能保证在任何地方都能正常工作。等效地:它包含一个可能会或可能不会显示自己的错误。
当然,有很多方法可以打破这些准则。有时,有充分的理由违反准则,但是这种情况极为罕见。
c int some_var; /* Do not do this in a header!!! */
注意1:如果标头定义的变量没有extern
关键字,则每个包含标头的文件都会创建该变量的临时定义。如前所述,这通常会起作用,但是C标准不能保证它会起作用。
c int some_var = 13; /* Only one source file in a program can use this */
注意2:如果标头定义并初始化了变量,则给定程序中只有一个源文件可以使用标头。由于标题主要用于共享信息,因此创建只能使用一次的标题有点愚蠢。
c static int hidden_global = 3; /* Each source file gets its own copy */
注意3:如果标头定义了一个静态变量(带有或不带有初始化),则每个源文件都会以其自己的“全局”变量专用版本结束。
例如,如果变量实际上是一个复杂的数组,则可能导致极端的代码重复。有时候,这可能是达到某种效果的明智方法,但这是非常不寻常的。
使用我首先展示的标题技术。它在任何地方都能可靠运行。特别要注意的global_variable
是,每个声明它的头都包含在使用它的每个文件中,包括定义它的文件。这样可以确保所有内容都是自洽的。
在声明和定义函数时也会遇到类似的问题-适用类似的规则。但是问题是关于变量的,所以我只保留了变量的答案。
如果您不是经验丰富的C程序员,则可能应该在这里停止阅读。
后期大修
有时(合法地)对这里描述的“标头中的声明,源中的定义”机制提出的一个关注是,有两个文件需要保持同步-标头和源。通常会观察到可以使用宏,以便标头起双重作用-通常声明变量,但是当在包含标头之前设置了特定的宏时,它会定义变量。
另一个问题可能是,需要在多个“主程序”的每一个中定义变量。这通常是一个虚假的担忧。您可以简单地引入一个C源文件来定义变量,并将每个程序产生的目标文件链接起来。
一个典型的方案就是这样使用原始的全局变量来实现的file3.h
:
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#else
#define EXTERN extern
#endif /* DEFINE_VARIABLES */
EXTERN int global_variable;
#define DEFINE_VARIABLES
#include "file3a.h" /* Variable defined - but not initialized */
#include "prog3.h"
int increment(void) { return global_variable++; }
#include "file3a.h"
#include "prog3.h"
#include <stdio.h>
void use_it(void)
{
printf("Global variable: %d\n", global_variable++);
}
接下来的两个文件完成了以下内容的源代码prog3
:
extern void use_it(void);
extern int increment(void);
#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且不支持宏中的变量参数列表,没有简单的方法来处理任意长的初始化程序。)
#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
#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; }
#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
is 的第一个参数是{ 41
,其余参数(在此示例中为单数)为43 }
。如果没有C99或对宏的变量参数列表的类似支持,则需要包含逗号的初始化程序将非常有问题。
丹尼斯·克尼亚热夫file3b.h
(Denis Kniazhevfileba.h
)包含(而不是)
正确的标题
接下来的两个文件完成了以下内容的源代码prog4
:
extern int increment(void);
extern int oddball_value(void);
extern void use_them(void);
#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
。应保护所有标头免于重新包含,以便类型定义(枚举,结构或联合类型,或通常为typedef)不会引起问题。标准技术是将标头的主体包装在标头防护中,例如:
#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED
...contents of header...
#endif /* FILE3B_H_INCLUDED */
标头可能会间接包含两次。例如,如果file4b.h
包含file3b.h
未显示的类型定义,并且file1b.c
需要同时使用header file4b.h
和file3b.h
,那么您还有一些棘手的问题需要解决。显然,您可以将标题列表修改为仅包含file4b.h
。但是,您可能不知道内部依赖关系-理想情况下,代码应继续运行。
此外,它开始变得棘手,因为您可能在包含file4b.h
之前就包含file3b.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
几次,但这是表明该机制的工作最简单的方法。这意味着,如果标头被间接包含两次,那也是安全的。
这项工作的限制是:
定义或声明全局变量的标头本身不能定义任何类型。
在包含应定义变量的标题之前,立即定义宏DEFINE_VARIABLES。
定义或声明变量的标题具有风格化的内容。
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#define INITIALIZE(...) = __VA_ARGS__
#else
#define EXTERN extern
#define INITIALIZE(...) /* nothing */
#endif /* DEFINE_VARIABLES */
#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 */
/* 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 */
#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; }
#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;
}
#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; }
#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
:
#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
:
/* 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"
#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
)。
/* 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; }
/* 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 */
/* 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
:
#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;
}
#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
变量是在另一个转换单元中定义的变量的声明(感谢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 int test1_var;
改为int test1_var;
,则链接器(gcc 5.4.0)仍然可以通过。那么,extern
在这种情况下真的需要吗?
Extern是用于声明变量本身位于另一个翻译单元中的关键字。
因此,您可以决定在翻译单元中使用变量,然后从另一个变量中访问它,然后在第二个变量中,将其声明为extern,链接器将解析该符号。
如果不将其声明为extern,则将获得2个名称相同但完全不相关的变量,并且会出现该变量的多个定义的错误。
extern告诉编译器信任您该变量的内存在其他地方声明,因此它不会尝试分配/检查内存。
因此,您可以编译一个引用了extern的文件,但是如果未在某处声明该内存,则无法链接。
对于全局变量和库很有用,但是很危险,因为链接器不进行检查。
declare | define | initialize |
----------------------------------
extern int a; yes no no
-------------
int a = 2019; yes yes yes
-------------
int a; yes yes no
-------------
声明不会分配内存(必须为内存分配定义变量),但是定义会。这只是对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);
}
example1.c
int a = 5;
现在,当您使用以下命令将两个文件编译在一起时:
步骤1)cc -o ex example.c example1.c步骤2)./ ex
您将获得以下输出:a的值为<5>
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
该系统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
用于一个first.c
文件可以完全访问另一个second.c
文件中的全局参数。
本extern
可以在声明first.c
文件中或在任何的头文件first.c
包括。
extern
声明应该在标头中,而不是in中first.c
,这样,如果类型更改,则声明也将更改。同样,声明变量的标头应包括在内,second.c
以确保定义与声明一致。标头中的声明是将所有内容粘合在一起的粘合剂;它允许分别编译文件,但确保它们对全局变量的类型具有一致的视图。
使用xc8时,您必须谨慎地在每个文件中声明一个与同一类型相同的变量,错误地int
在一个文件中声明一个,在另一个文件中声明char
。这可能导致变量损坏。
这个问题在15年前的微芯片论坛上得到了很好的解决//参见“ http:www.htsoft.com” // “ forum / all / / 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
。
较旧的论坛帖子可能对此进行了更清晰的解释。但是,gotcha
当使用允许您在一个文件中声明一个变量,然后在另一个文件中将其声明为另一种类型的编译器时,这是一个真正的潜力。与之相关的问题是,如果您说将testing_mode声明为另一个文件中的int值,它将认为它是一个16位var,并覆盖ram的其他部分,从而可能损坏另一个变量。调试困难!
我用来允许头文件包含外部引用或对象的实际实现的一种非常简短的解决方案。实际上包含该对象的文件#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