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

14 浏览
0 Comments

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

我知道C语言中的全局变量有时会带有extern关键字。什么是extern变量?它的声明是什么样的?它的作用域是什么?

这与在源文件之间共享变量有关,但具体是如何工作的呢?我在哪里使用extern

admin 更改状态以发布 2023年5月19日
0
0 Comments

extern 变量是一个变量的声明(感谢sbi提供的修正),它在另一个翻译单元中定义。这意味着变量的存储在另一个文件中分配。

假设您有两个 .c 文件 test1.ctest2.c。如果您在 test1.c 中定义全局变量 int test1_var; 并且您想要在 test2.c 中访问此变量,则必须在 test2.c 中使用 extern int test1_var;

完整示例:

$ cat test1.c 
int test1_var = 5;
$ cat test2.c
#include 
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

0
0 Comments

只有当你构建的程序由多个源文件链接在一起时,在其中一些源文件(例如file1.c)定义的变量需要在其他源文件(如file2.c)中引用时,extern才有意义。

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

  • 当编译器知道一个变量的存在(和它的类型)时,变量被声明;此时并没有分配变量的存储空间。

  • 当编译器分配变量的存储空间时,变量被定义

你可以多次声明一个变量(虽然一次就足够了),但是在给定范围内只能定义它一次。变量定义也是一个声明,但不是所有变量声明都是定义。

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

声明和定义全局变量的清晰、可靠的方法是使用头文件来包含变量的extern声明。

头文件被定义变量的一个源文件和引用变量的所有源文件所包含。对于每个程序,一个源文件(仅一个源文件)定义变量。同样,一个头文件(仅一个头文件)应该声明变量。

头文件至关重要;它可以在独立的转换单元(Think源文件)之间进行交叉检查,确保一致性。

虽然有其他方法可以实现,但这种方法简单且可靠。
它由file3.hfile1.cfile2.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 
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 
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声明变量。

  • 尽可能避免全局变量——使用函数代替。

  • 此答案的源代码和文本可在我的SOQ(Stack Overflow Questions)GitHub存储库的src/so-0143-3204子目录中获取。

    如果您不是有经验的C程序员,可以(也许应该)在此停止阅读。

    定义全局变量的不太好的方法

    在某些(确实是许多)C编译器中,您可以采用所谓的“通用”方式定义变量。“通用”在这里是指Fortran中用于在源文件之间共享变量的技术,使用(可能命名的)COMMON块。这里发生的是,多个文件提供变量的试探定义。只要不超过一个文件提供一个已初始化定义,那么各个文件最终会共享变量的共同单一定义:

    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 
    long l = 9;   /* Do not do this in portable code */
    void put(void) { printf("l = %ld\n", l); }
    

    这种技巧不符合C标准和“单一定义规则”的字面意思,它是官方未定义的行为:

    J.2 未定义行为

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

    §6.9 外部定义 ¶5

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

    161)因此,如果具有外部链接的标识符未在表达式中使用,则不需要为其提供外部定义。

    然而,C标准还将其列入信息性附录J中,作为常见扩展之一。

    J.5.11 多个外部定义

    一个标识符的对象可能有一个以上的外部定义,有或没有显式使用关键词extern;如果这些定义不一致或有一个以上被初始化,那么行为是未定义的(6.9.2)。

    因为这种技巧并不总是被支持,最好避免使用它,尤其是如果你的代码需要可移植性。使用这种技巧,你可能也会遇到无意中的类型转换。

    如果上述文件中有一个将l声明为double而不是long的话,C语言的类型不安全的链接器可能不会发现不匹配。如果你在一台64位的longdouble的机器上,甚至不会得到警告;在一台32位的long和64位的double的机器上,你可能会收到不同大小的警告——链接器会使用最大的大小,而Fortran程序会选择任何公共块中的最大大小。

    请注意,GNU 10.1.0版本的GCC,于2020年5月7日发布,更改了默认的编译选项,使用-fno-common,这意味着默认情况下,上述代码不再链接,除非你用-fcommon覆盖默认选项(或使用属性等——请查看链接)。


    接下来两个文件完成了prog2的源代码:

    prog2.h

    extern void dec(void);
    extern void put(void);
    extern void inc(void);
    

    prog2.c

    #include "prog2.h"
    #include 
    int main(void)
    {
        inc();
        put();
        dec();
        put();
        dec();
        put();
    }
    

    • prog2使用了prog2.cfile10.cfile11.cfile12.cprog2.h

    警告

    正如在此处的注释中所述,以及我对类似问题的回答中所述,对全局变量使用多个定义会导致未定义的行为(J.2;§6.9),这是标准的方式表明“任何事情都有可能发生”。
    其中可发生的一件事是程序表现符合您的预期;J.5.11大致表示:“您可能比您应得的更幸运”。
    但是,一个依赖于多个外部变量定义的程序-无论是否带有显式的“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  */
    

    注意三:如果头文件定义了静态变量(带有或不带有初始化),那么每个源文件都会得到其自己私有的"全局"变量。

    例如,如果变量实际上是一个复杂的数组,这可能会导致代码的极度重复。这在很少情况下是实现某种效果的明智方式,但这非常不寻常。


    总结

    使用我首先展示的头文件技术。

    它在各个地方都能可靠地工作。

    特别要注意的是,声明global_variable的头文件包含在使用它的每个文件中,包括定义它的文件。

    这确保了一切的自洽性。

    声明和定义函数时会出现类似的问题,应用同样的规则。

    但这个问题是关于变量的,所以我只回答有关变量的问题。

    原始回答结束

    如果你不是经验丰富的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 
    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 
    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发现的错误。

    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 
    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.h

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

    prog4.c

    #include "file3b.h"
    #include "prog4.h"
    #include 
    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 的体来进行定义,但你可能需要在一个单独的翻译单元(TU——一个源文件和它使用的头文件的组合)中都需要。

    带有变量定义的多重包含

    然而,可以在不太苛刻的限制下实现。我们引入一组新的文件名:

    • external.h 用于定义 EXTERN 宏,等等。

    • file1c.h 用于定义类型(特别是 struct oddball,即 oddball_struct 的类型)。

    • file2c.h 用于定义或声明全局变量。

    • file3c.c 用于定义全局变量。

    • file4c.c 简单地使用全局变量。

    • file5c.c 展示了您可以先声明再定义全局变量。

    • file6c.c 展示了您可以先定义然后(尝试)声明全局变量。

    在这些示例中,file5c.cfile6c.c 直接多次包含头文件
    file2c.h,但这是展示机制最简单的方式。这意味着如果头文件间接包含两次,它也是安全的。

    这种实现的限制是:

    1. 定义或声明全局变量的头文件本身不能定义任何类型。

    2. 在包含应该定义变量的头文件之前,定义宏 DEFINE_VARIABLES。

    3. 定义或声明变量的头文件具有样式化内容。

    external.h

    /*
    ** This header must not contain header guards (like  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 
    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; }
    


    下一个源文件为 prog5, prog6prog7 提供了主程序:

    prog5.c

    #include "file2c.h"
    #include 
    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 来部分解决这个问题:

    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  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)。

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


    下面两个文件完成了prog8prog9的源代码:

    prog8.c

    #include "file2d.h"
    #include 
    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 
    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.h`和`externdef.h`)表明可以使其正常工作。显然,可以很容易地创建一个头文件生成器脚本,以为变量定义和声明头文件提供标准模板。

    注:这些是玩具程序,只有足够的代码使它们稍微有些趣味。实例中存在重复,可以消除,但为了简化教学解释而未消除。例如:`prog5.c`和`prog8.c`之间的差异在于所包含的头文件名称。可以重新组织代码,以使`main()`函数不重复,但它会掩盖更多内容。

    0