首页 文章

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

提问于
浏览
854

我知道C中的全局变量有时会有 extern 关键字 . 什么是 extern 变量?宣言是什么样的?它的范围是什么?

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

15 回答

  • 15

    我喜欢将外部变量视为您对编译器的承诺 .

    遇到extern时,编译器只能找到它的类型,而不是它“存在”的位置,因此它无法解析引用 .

    你告诉它,“相信我 . 在链接时,这个引用将是可解析的 . ”

  • 18

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

    重要的是understand the difference between defining a variable and declaring a variable

    • 当编译器被告知变量存在时(和它的类型),变量是 declared ;它不会在该点为变量分配存储空间 .

    • 当编译器为变量分配存储时,变量为 defined .

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

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

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

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

    虽然还有其他方法,但这种方法简单可靠 . 它由 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 <stdio.h>
    
    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 <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 的生成文件 . 它将适用于大约在千禧年之后产生的大多数版本的 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 声明变量 .

    • 尽可能避免全局变量 - 改为使用函数 .

    这个答案的源代码和文本可以在src / so-0143-3204子目录中的GitHub上的SOQ(Stack Overflow Questions)存储库中找到 .

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

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

    对于一些(实际上很多)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 = %d\n", i); }
    

    这种技术不符合C标准的字母和“一个定义规则” - 它是官方未定义的行为:

    J.2未定义的行为使用具有外部链接的标识符,但在程序中不存在标识符的唯一外部定义,或者不使用标识符,并且标识符存在多个外部定义(6.9) . §6.9外部定义¶5外部定义是外部声明,也是函数(内联定义除外)或对象的定义 . 如果在表达式中使用通过外部链接声明的标识符(除了作为sizeof或_Alignof运算符的操作数的一部分,其结果是整数常量),则整个程序中的某个地方应该只有一个标识符的外部定义;否则,不应超过一个.161)161)因此,如果在表达式中没有使用外部链接声明的标识符,则不需要外部定义 .

    但是,C标准也将其列为资料性附件J中的一个Common extensions .

    J.5.11多个外部定义对象的标识符可能有多个外部定义,有或没有明确使用关键字extern;如果定义不一致,或者初始化多个,则行为未定义(6.9.2) .

    由于并不总是支持此技术,因此最好避免使用它,尤其是在代码需要可移植的情况下 . 使用这种技术,您最终也可能会遇到无意的类型惩罚 . 如果其中一个文件声明 idouble 而不是 int ,C 's type-unsafe linkers probably would not spot the mismatch. If you'在具有64位 intdouble 的机器上,则您甚至不会收到警告;在具有32位 int 和64位 double 的计算机上,您可能会收到有关不同大小的警告 - 链接器将使用最大的大小,正如Fortran程序将占用任何公共块的最大大小一样 .


    接下来的两个文件完成 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 .

    警告

    正如我在这里的评论中所指出的,并且在我对类似question的回答中所述,对全局变量使用多个定义会导致未定义的行为(J.2;§6.9),这是标准的's way of saying 458913 . One of the things that can happen is that the program behaves as you expect; and J.5.11 says, approximately, 458914 . But a program that relies on multiple definitions of an extern variable — with or without the explicit ' extern'关键字 - 不是严格的符合计划,并不保证在任何地方工作 . 同等地:它包含一个可能会或可能不会显示自身的错误 .

    违反准则

    当然,有很多方法可以打破这些指导方针 . 偶尔,有可能有理由违反指导方针,但这种情况极不寻常 .

    faulty_header.h

    int some_var;    /* Do not do this in a header!!! */
    

    注1:如果 Headers 定义了不带 extern 关键字的变量,那么包含 Headers 的每个文件都会创建变量的暂定定义 . 如前所述,这通常会起作用,但C标准并不能保证它能够正常工作 .

    broken_header.h

    int some_var = 13;    /* Only one source file in a program can use this */
    

    注2:如果标头定义并初始化变量,则给定程序中只有一个源文件可以使用标头 . 由于 Headers 主要用于共享信息,因此创建一个只能使用一次的信息有点愚蠢 .

    seldom_correct.h

    static int hidden_global = 3;   /* Each source file gets its own copy  */
    

    注3:如果标头定义了一个静态变量(有或没有初始化),那么每个源文件最终都有自己的私有版本的'global'变量 .

    例如,如果变量实际上是一个复杂的数组,则可能导致代码的极端重复 . 偶尔,它可以是实现某种效果的合理方式,但这是非常不寻常的 .


    摘要

    使用我首先展示的 Headers 技术 . 它可靠,无处不在 . 请特别注意,声明 global_variable 的标头包含在使用它的每个文件中 - 包括定义它的文件 . 这确保了一切都是自洽的 .

    声明和定义功能也会出现类似的问题 - 适用类似的规则 . 但问题是关于变量的具体问题,所以我只保留了变量的答案 .

    原始答案结束

    如果您不是经验丰富的C程序员,您可能应该停止阅读 .


    延迟重大增加

    避免代码重复

    有时(并且合法地)提出关于这里描述的' Headers 中的声明,源中的定义'机制的一个问题是有两个文件要保持同步 - Headers 和源 . 这通常会随后观察到可以使用宏来使头部具有双重功能 - 通常声明变量,但是在包含标头之前设置特定的宏时,它会定义变量 .

    另一个问题可能是需要在许多“主程序”中定义变量 . 这通常是一个虚假的问题;您可以简单地引入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.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识别的bug

    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或对宏的可变参数列表的类似支持,那么需要包含逗号的初始化器非常有问题 .

    每个Denis Kniazhev都包含正确的头文件3b.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 <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 .

    Header Guards

    应保护任何标头不被重新包含,因此类型定义(枚举,结构或联合类型或一般的typedef)不会导致问题 . 标准技术是将 Headers 的主体包裹在 Headers 保护中,例如:

    #ifndef FILE3B_H_INCLUDED
    #define FILE3B_H_INCLUDED
    
    ...contents of header...
    
    #endif /* FILE3B_H_INCLUDED */
    

    Headers 可能间接包含两次 . 例如,如果 file4b.h 包含 file3b.h 用于未显示的类型定义,并且 file1b.c 需要同时使用 Headers file4b.hfile3b.h ,那么您需要解决一些棘手的问题 . 显然,您可以修改 Headers 列表以仅包含 file4b.h . 但是,您可能不了解内部依赖关系 - 理想情况下,代码应该继续工作 .

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

    因此,您需要最多包含 file3b.h 的主体一次用于声明,最多只包含一次用于定义,但您可能需要在单个转换单元(TU - 源文件和它使用的标头的组合)中 .

    包含变量定义的多个包含

    但是,它可以在不太合理的约束下完成 . 我们来介绍一组新的文件名:

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

    • file1c.h 定义类型(特别是 struct oddballoddball_struct 的类型) .

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

    • file3c.c 定义了全局变量 .

    • file4c.c 只使用全局变量 .

    • file5c.c 表明您可以声明然后定义全局变量 .

    • file6c.c 表明您可以定义然后(尝试)声明全局变量 .

    在这些示例中, file5c.cfile6c.c 多次直接包含 Headers file2c.h ,但这是显示该机制有效的最简单方法 . 这意味着如果 Headers 间接包含两次,那么它也是安全的 .

    这项工作的限制是:

    • 定义或声明全局变量的标头本身可能不定义任何类型 .

    • 在包含应定义变量的标头之前,立即定义宏DEFINE_VARIABLES .

    • 定义或声明变量的 Headers 具有程式化的内容 .

    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: %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 )中,则只会遇到问题 . 除了"don't do it"之外,还有一个简单的方法 .

    您可以通过将 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 */
    

    如果 Headers 包含 #undef DEFINE_VARIABLES ,则问题变为'?'如果从 Headers 中省略它并用 #define#undef 包装任何定义调用:

    #define DEFINE_VARIABLES
    #include "file2c.h"
    #undef DEFINE_VARIABLES
    

    在源代码中(所以 Headers 永远不会改变 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.hfile2d.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 <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 .


    但是,这些问题在实践中相对不太可能发生,特别是如果您采用标准建议

    避免全局变量


    这个博览会是否遗漏了什么?

    忏悔:这里概述的'avoiding duplicated code'方案是因为问题影响了我工作的一些代码(但不拥有),并且是对答案第一部分概述的方案的一个琐碎关注 . 但是,原始方案只留下两个地方进行修改以保持变量定义和声明同步,这是将exernal变量声明分散在整个代码库中的一大步(当总共有数千个文件时真正重要) . 但是,名称为 fileNc.[ch] (加上 external.hexterndef.h )的文件中的代码表明它可以正常工作 . 显然,创建一个 Headers 生成器脚本来为定义和声明头文件的变量提供标准化模板并不困难 .

    注意:这些玩具程序只有几乎没有足够的代码来使它们略微有趣 . 在示例中可以删除重复,但不是为了简化教学解释 . (例如: prog5.cprog8.c 之间的区别是包含的其中一个 Headers 的名称 . 可以重新组织代码,以便不重复 main() 函数,但它隐藏的内容比它显示的要多 . )

  • 7

    在C中,文件中的变量表示example.c具有本地范围 . 编译器期望变量将在同一个文件example.c中定义它,并且当它找不到相同时,它会抛出一个错误 . 另一方面,一个函数默认具有全局范围 . 因此,您不必明确提及编译器"look dude...you might find the definition of this function here" . 对于包含包含其声明的文件的函数就足够了 . (实际上称为头文件的文件) . 例如,考虑以下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 step 2)./ ex

    您将获得以下输出:a的值为<5>

  • 1542

    GCC ELF Linux implementation

    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 Update ELF spec "Symbol Table"章解释说:

    SHN_UNDEF此节表索引表示符号未定义 . 当链接编辑器将此目标文件与定义指示符号的另一个目标文件组合在一起时,该文件对该符号的引用将链接到实际定义 .

    这基本上是C标准给 extern 变量的行为 .

    从现在开始,链接器的作用是创建最终程序,但 extern 信息已经从源代码中提取到目标文件中 .

    在GCC 4.8上测试 .

  • 5

    extern告诉编译器要相信这个变量的内存是在别处声明的,所以它不会尝试分配/检查内存 .

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

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

  • 4

    extern keyword is used with the variable for its identification as a global variable.

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

  • 4

    extern 仅表示变量在别处定义(例如,在另一个文件中) .

  • 3

    Extern是用于声明变量本身位于另一个转换单元中的关键字 .

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

    如果你没有将它声明为extern,你将获得两个名为相同但根本不相关的变量,以及变量的多个定义的错误 .

  • 0

    使用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 .

    extern unsigned char testing_mode;
    

    但它将被正确声明为 unsigned char .

    旧的论坛帖子可能更清楚地解释了这一点 . 但是,当使用允许您在一个文件中声明变量然后在另一个文件中将其声明为另一个类型的编译器时,这是一个真正的潜力 gotcha . 与此相关的问题是,如果你说在另一个文件中声明testing_mode为int,它会认为它是16位var并覆盖ram的其他部分,可能会破坏另一个变量 . 很难调试!

  • 8

    对extern的正确解释是你告诉编译器一些东西 . 您告诉编译器,尽管现在不存在,但是声明的变量将以某种方式由链接器找到(通常在另一个对象(文件)中) . 无论你是否有一些外部声明,链接器将是幸运的人找到所有东西并把它放在一起 .

  • 111

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

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

  • 23

    extern 变量是在另一个翻译单元中定义的变量的声明(由于sbi用于校正) . 这意味着变量的存储空间分配在另一个文件中 .

    假设你有两个 .c -files 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 <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
    
  • 35

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

    如果您不希望程序访问您的变量或函数,则使用 static ,它告诉编译器此变量或函数不能在此模块之外使用 .

  • 11

    添加 extern 会将变量定义转换为变量声明 . 有关声明和定义之间的区别,请参见this thread .

  • 6

    使用 extern ,因此一个 first.c 文件可以完全访问另一个 second.c 文件中的全局参数 .

    extern 可以在 first.c 文件中声明,也可以在 first.c 包含的任何头文件中声明 .

相关问题