如何使用extern在源文件之间共享变量?

我知道C中的全局变量有时具有extern关键字。 什么是extern变量? 声明是什么样的? 它的范围是什么?

这与跨源文件共享变量有关,但是它如何精确地工作? 我在哪里使用extern


#1楼

extern的正确解释是您向编译器告知了一些信息。 您告诉编译器,尽管目前尚不存在,但声明的变量将以某种方式由链接器(通常在另一个对象(文件)中)找到。 然后,无论您是否有外部声明,链接器都将是找到所有内容并将其放在一起的幸运者。


#2楼

在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>


#3楼

首先, extern关键字不用于定义变量。 而是用于声明变量。 我可以说extern是存储类,而不是数据类型。

extern用于让其他C文件或外部组件知道此变量已在某处定义。 示例:如果您正在构建库,则无需在库本身的某个位置强制定义全局变量。 该库将直接编译,但是在链接文件时,它将检查定义。


#4楼

extern关键字与变量一起使用,以将其标识为全局变量。

它也表示您可以在任何文件中使用使用extern关键字声明的变量,尽管该变量是在其他文件中声明/定义的。


#5楼

extern允许程序的一个模块访问在程序的另一个模块中声明的全局变量或函数。 通常,您在头文件中声明了外部变量。

如果您不希望程序访问变量或函数,请使用static ,它告诉编译器该变量或函数不能在该模块外部使用。


#6楼

Extern是用于声明变量本身位于另一个翻译单元中的关键字。

因此,您可以决定在翻译单元中使用变量,然后从另一个变量中访问它,然后在第二个变量中,将其声明为extern,链接器将解析该符号。

如果不将其声明为extern,则将获得2个名称相同但完全不相关的变量,以及多个变量定义的错误。


#7楼

extern变量是在另一个转换单元中定义的变量的声明(感谢sbi进行更正)。 这意味着该变量的存储在另一个文件中分配。

假设你有两个.c -files test1.ctest2.c 。 如果定义一个全局变量int test1_var;test1.c ,您想在test2.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

#8楼

添加extern会将变量定义转换为变量声明 。 请参阅此线程 ,以了解声明和定义之间的区别。


#9楼

extern告诉编译器信任您该变量的内存在其他地方声明,因此它不会尝试分配/检查内存。

因此,您可以编译一个引用了extern的文件,但是如果未在某处声明该内存,则无法链接。

对于全局变量和库很有用,但是很危险,因为链接器不进行检查。


#10楼

仅当要构建的程序由多个链接在一起的源文件组成时才使用extern ,其中需要在其他源文件(例如file2.c引用例如在源文件file1.c定义的某些变量file2.c

重要的是要了解定义变量和声明变量之间的区别

  • 当编译器被告知存在一个变量(这就是它的类型)时,就声明一个变量。 此时,它不会为变量分配存储空间。
  • 当编译器为变量分配存储空间时,将定义一个变量。

您可以多次声明一个变量(尽管一次就足够了); 您只能在给定范围内定义一次。 变量定义也是声明,但并非所有变量声明都是定义。

声明和定义全局变量的最佳方法

声明和定义全局变量的干净,可靠的方法是使用头文件包含变量的extern 声明

标头包含在一个定义变量的源文件中,并且包含在所有引用该变量的源文件中。 对于每个程序,一个源文件(只有一个源文件)定义了变量。 同样,一个头文件(只有一个头文件)应声明该变量。 头文件至关重要。 它可以在独立的TU(翻译单元,即源文件)之间进行交叉检查,并确保一致性。

尽管还有其他方法可以执行此操作,但此方法简单可靠。 它由file3.hfile1.cfile2.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 ; 编译器不在乎-最终,只要您保持一致,至少在源文件中,我也不会这样做。

程序1

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.cfile1.cfile2.cfile3.hprog1.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}

指导方针

只能由专家打破规则,并且有充分的理由:

  • 头文件仅包含变量的extern声明,而不能包含static或不合格的变量定义。
  • 对于任何给定的变量,只有一个头文件进行声明(SPOT —单点真相)。
  • 源文件从不包含变量的extern声明-源文件始终包含声明它们的(唯一)标头。
  • 对于任何给定的变量,恰好一个源文件定义了该变量,最好也将其初始化。 (尽管不需要显式地初始化为零,但它无害且可以带来一些好处,因为在程序中只能存在一个对特定全局变量的初始化定义)。
  • 定义变量的源文件还包括标头,以确保定义和声明一致。
  • 函数永远不需要使用extern声明变量。
  • 尽可能避免使用全局变量,而应使用函数。

这个答案的源代码和文本可在GitHub上的src / so-0143-3204子目录中的SOQ (堆栈溢出问题)存储库中找到。

如果您不是经验丰富的C程序员,则可以(也许应该)在这里停止阅读。

定义全局变量的方法不是很好

使用某些(实际上是许多)C编译器,您也可以摆脱所谓的“通用”变量定义。 “公共”在这里是指Fortran中使用(可能是命名的)COMMON块在源文件之间共享变量的技术。 这里发生的是,许多文件中的每一个都提供了变量的临时定义。 只要提供一个初始化的定义的文件不超过一个,那么各种文件最终都会共享一个变量的通用单个定义:

文件10.c

#include "prog2.h"

int i;   /* Do not do this in portable code */

void inc(void) { i++; }

文件11.c

#include "prog2.h"

int i;   /* Do not do this in portable code */

void dec(void) { i--; }

文件12.c

#include "prog2.h"
#include <stdio.h>

int i = 9;   /* Do not do this in portable code */

void put(void) { printf("i = %d\n", i); }

该技术不符合C标准的字母和“一个定义规则” —正式是未定义的行为:

J.2未定义行为

使用了具有外部链接的标识符,但是在程序中,标识符不完全存在一个外部定义,或者未使用标识符,并且标识符具有多个外部定义(6.9)。

§6.9外部定义¶5

外部定义是外部声明,它也是函数(内联定义除外)或对象的定义。 如果在表达式中使用了用外部链接声明的标识符(不是作为sizeof_Alignof运算符的操作数的一部分,其结果是一个整数常量),则在整个程序中的某个位置应有一个标识符的外部定义; 否则,不得超过一个。 161)

161)因此,如果在表达式中未使用通过外部链接声明的标识符,则无需为其定义外部。

但是,C标准还在参考性附录J中将其列为通用扩展之一

J.5.11多个外部定义

一个对象的标识符可能有多个外部定义,无论是否明确使用关键字extern都可以; 如果定义不一致,或者初始化了多个定义,则行为未定义(6.9.2)。

由于并不总是支持此技术,因此最好避免使用它, 尤其是在您的代码需要可移植的情况下 。 使用这种技术,您还可能最终会意外地进行类型调整。 如果其中一个文件将i声明为double而不是int ,则C的类型不安全的链接程序可能不会发现不匹配。 如果您使用的是64位intdouble ,那么您甚至都不会收到警告。 在具有32位int和64位double ,您可能会收到有关大小不同的警告-链接器将使用最大大小,就像Fortran程序将占用所有常见块的最大大小一样。


接下来的两个文件完善了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.cfile10.cfile11.cfile12.cprog2.h

警告

如此处评论中所述,以及如我对类似问题的回答所述,对全局变量使用多个定义会导致未定义的行为(J.2;第6.9节),这是标准所说的“可能发生的一切”的方式。 可能发生的事情之一是程序的行为符合预期。 J.5.11大概说,“您可能比应有的机会更幸运”。 但是,依赖于extern变量的多个定义(带有或不带有显式的'extern'关键字)的程序并不是严格符合要求的程序,不能保证在任何地方都能工作。 等效地:它包含一个可能会或可能不会显示自己的错误。

违反准则

当然,有很多方法可以打破这些准则。 有时,有充分的理由违反准则,但是这种情况极为罕见。

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_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++; }

file2a.c

#include "file3a.h"
#include "prog3.h"
#include <stdio.h>

void use_it(void)
{
    printf("Global variable: %d\n", global_variable++);
}

接下来的两个文件完善了prog3的源prog3

程序3

extern void use_it(void);
extern int increment(void);

程序3

#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.cfile1a.cfile2a.cfile3a.hprog3.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识别的错误

文件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; }

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或对宏的变量参数列表的类似支持,则需要包含逗号的初始化程序将非常有问题。

每个Denis Kniazhev包含正确的头文件file3b.h (而不是fileba.h


接下来的两个文件完善了prog4的源prog4

prog4.h

extern int increment(void);
extern int oddball_value(void);
extern void use_them(void);

程序4

#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.cfile1b.cfile2b.cprog4.hfile3b.h

标头护卫

应保护所有标头免于重新包含,以便类型定义(枚举,结构或联合类型,或通常为typedef)不会引起问题。 标准技术是将标头的主体包装在标头防护中,例如:

#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED

...contents of header...

#endif /* FILE3B_H_INCLUDED */

标头可能会间接包含两次。 例如,如果file4b.h包括file3b.h对于未示出的类型定义,并file1b.c需要同时使用头file4b.hfile3b.h ,那么你有一些棘手的问题需要解决。 显然,您可以修改标题列表,使其仅包含file4b.h 。 但是,您可能不知道内部依赖关系-理想情况下,代码应继续运行。

此外,它开始变得棘手,因为您可能在包含file4b.h之前先包含file3b.h来生成定义,但是file3b.h上的常规头保护file3b.h会阻止重新包含头。

因此,您需要最多包含一次file3b.h的主体用于声明,最多包含一次定义,但是您可能需要在一个翻译单元(TU-源文件及其使用的头文件的组合)中同时包含两者。 。

包含变量定义的多重包含

但是,可以在不太合理的约束条件下完成此操作。 让我们介绍一组新的文件名:

  • 用于EXTERN宏定义的external.h等。
  • file1c.h来定义类型(特别是struct oddballoddball_struct的类型)。
  • file2c.h来定义或声明全局变量。
  • 定义全局变量的file3c.c
  • 仅使用全局变量的file4c.c
  • file5c.c ,显示您可以声明然后定义全局变量。
  • file6c.c显示可以定义,然后(尝试)声明全局变量。

在这些示例中, file5c.cfile6c.c直接包含头文件file2c.h ,但这是表明该机制有效的最简单方法。 这意味着,如果标头被间接包含两次,那也是安全的。

这项工作的限制是:

  1. 定义或声明全局变量的标头本身不能定义任何类型。
  2. 在包含应该定义变量的标题之前,立即定义宏DEFINE_VARIABLES。
  3. 定义或声明变量的标题具有风格化的内容。

外部

/*
** 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: %d\n", 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; }

下一个源文件完善了prog5prog6prog7的源代码(提供了一个主程序):

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.cfile3c.cfile4c.cfile1c.hfile2c.hexternal.h
  • prog6使用prog5.cfile5c.cfile4c.cfile1c.hfile2c.hexternal.h
  • prog7使用prog5.cfile6c.cfile4c.cfile1c.hfile2c.hexternal.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"

外部定义

/*
** 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; }

文件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 */

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; }

接下来的两个文件完善了prog8prog9的源代码:

程序8

#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使用prog8.cfile7c.cfile9c.c
  • prog9使用prog8.cfile8c.cfile9c.c

但是,在实践中相对不太可能出现问题,尤其是如果您采用标准建议

避免全局变量


这次博览会有什么遗漏吗?

坦白 :之所以开发此处概述的“避免重复代码”方案,是因为该问题会影响我正在处理的某些代码(但不属于自己),并且是对答案第一部分中概述的方案的关注。 但是,原始方案仅使您有两个地方可以进行修改以使变量定义和声明保持同步,这比将外部变量声明分散在整个代码库中要向前迈出了一大步(当总共有数千个文件时,这确实很重要) 。 但是,名称为fileNc.[ch]的文件中的代码(加上external.hexterndef.h )表明可以使其正常工作。 显然,创建头生成器脚本为定义和声明头文件的变量提供标准化模板并不困难。

注意:这些是玩具程序,仅具有几乎不足以使它们有趣的代码。 示例中有很多重复可以删除,但并不是为了简化教学方法。 (例如: prog5.cprog8.c之间的区别是所包含的头文件之一的名称。可以重新组织代码,以便不重复main()函数,但是它将隐藏更多内容。比显示的要多。)


#11楼

我喜欢将extern变量视为对编译器的承诺。

遇到外部变量时,编译器只能找到其类型,而不能找到其“存在”的位置,因此它无法解析引用。

您告诉它,“相信我。在链接时,此引用将是可解决的。”


#12楼

extern以便一个first.c文件可以完全访问另一个second.c文件中的全局参数。

所述extern可以在被声明first.c文件中或在任何的头文件first.c包括。


#13楼

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 ++中是什么意思?


#14楼

extern仅仅意味着在其他地方(例如,在另一个文件中)定义了一个变量。


#15楼

使用xc8时,必须谨慎地在每个文件中声明与同一类型相同的变量,错误地在一个文件中声明一个int在另一个文件中声明一个char 。 这可能导致变量损坏。

这个问题在15年前的微芯片论坛中得到了巧妙解决/ *参见“ http:www.htsoft.com” // “ forum / all / 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

旧的论坛帖子可能对此进行了更清晰的解释。 但是,当使用允许您在一个文件中声明一个变量,然后在另一个文件中将其声明为另一种类型的编译器时,这确实是一个潜在的gotcha 。 与之相关的问题是,如果您说将testing_mode声明为另一个文件中的int值,它将认为它是一个16位var,并覆盖ram的其他部分,从而可能损坏另一个变量。 调试困难!


#16楼

                 declare | define   | initialize |
                ----------------------------------

extern int a;    yes          no           no
-------------
int a = 2019;    yes          yes          yes
-------------
int a;           yes          yes          no
-------------

声明不会分配内存(必须为内存分配定义变量),但是定义会。 这只是对extern关键字的另一种简单见解,因为其他答案确实很棒。


#17楼

我用来允许头文件包含外部引用或对象的实际实现的一种非常简短的解决方案。 实际包含该对象的文件只执行#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
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值