如何使用extern在源文件之间共享变量? [英] How do I use extern to share variables between source files?

查看:168
本文介绍了如何使用extern在源文件之间共享变量?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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

解决方案

使用 extern 只与您构建
的程序包含多个链接在一起的源文件相关,其中一些
变量已定义例如,源文件 file1.c 需要在其他源文件中引用
,例如 file2.c



重要的是要了解定义 a
变量,声明
变量



  • 当编译器被告知存在一个
    变量(这是它的类型)时,变量被声明;它不会为该变量分配
    存储。

  • 当编译器为
    分配存储时,变量是定义的


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



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



尽管还有其他的方法,但
声明和定义全局变量的干净可靠的方法是使用头文件 file3。 h
包含变量的 extern 声明。标题是
,由定义该变量的一个源文件以及所有引用该变量的
源文件包含。对于每个程序,一个源
文件(并且只有一个源文件)定义该变量。同样,一个
头文件(只有一个头文件)应该声明变量。



file3.h



  extern int global_variable; / *声明变量* / 



file1.c



  #includefile3.h/ *此处提供声明* / 
#includeprog1.h/ *函数声明* /

/ *此处定义的变量* /
int global_variable = 37; / *根据声明检查定义* /

int increment(void){return global_variable ++; }



file2.c



  #includefile3.h
#includeprog1.h
#include< stdio.h>

void use_it(void)
{
printf(全局变量:%d \ n,global_variable ++);
}

这是使用它们的最佳方法。






接下来的两个文件完成 prog1 的来源:



请注意,我在标头中的函数声明前使用关键字 extern (如 prog1.h ),以匹配标题中变量声明前面的 extern 。许多人不喜欢在函数前面使用 extern ;编译器不关心 - 最终,只要你一致,我也不会这样做。



prog1.h



  extern void use_it(void); //extern在这里是可选的;请参阅上面的注释
extern int increment(void); //extern在这里是可选的;请参阅上面的注释



prog1.c



  #includefile3.h
#includeprog1.h
#include< stdio.h>

int main(void)
{
use_it();
global_variable + = 19;
use_it();
printf(Increment:%d \\\
,increment());
返回0;

$ / code>




  • prog1 使用 prog1.c file1.c file2.c file3.h prog1.h






指南



规则仅由专家打破,只有很好的理由:


  • 头文件只包含 extern 变量声明 - 从来没有
    静态或非限定变量定义。

  • 对于任何给定的变量,只有一个头文件声明它(SPOT - $
  • 源文件永远不会包含 extern 变量声明 -
    源文件始终包括声明它们的(唯一)标题。

  • 对于任何给定的变量,只有一个源文件定义变量,
    最好也初始化它。 (尽管没有必要将
    明确初始化为零,但它没有任何损害,并且可以做一些很好的
    ,因为程序中只能有一个特定
    全局变量的初始化定义)。
  • 定义变量的源文件还包含
    的头文件,以确保定义和声明的一致性。

  • 函数不需要使用 extern 来声明变量。

  • 尽可能避免全局变量 - 使用函数。



  • 如果你不是经验丰富的C程序员,你可以(也可以
    )停止阅读。



    此答案的源代码和文本可在我的
    <一个href =https://github.com/jleffler/soq =nofollow noreferrer> SOQ
    存储在
    中的GitHub上 src / so-0143-3204
    子目录

    p>

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



    使用一些(实际上很多)C编译器,你可以逃脱什么是
    也称为变量的通用定义。 'Common'在这里将
    引用到Fortran中用于在源
    文件之间共享变量的技术,使用(可能命名为)COMMON块。这里发生的是,
    中的每一个都提供了
    变量的暂定义。只要不超过一个文件提供初始化的
    定义,那么各种文件最终会共享一个共同的
    变量定义:



    file10.c



      #includeprog2.h

    int i; / *不要在可移植代码中执行此操作* /

    void inc(void){i ++; }



    file11.c



      #includeprog2.h

    int i; / *不要在可移植代码中执行此操作* /

    void dec(void){i--; }



    file12.c



      #includeprog2.h
    #include< stdio.h>

    int i = 9; / *不要在可移植代码中执行此操作* /

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

    此技术不符合C标准的字母和
    '一个定义规则,但C标准在其一个定义规则中将其列为常见变体

    因为这种技术并不总是被支持的,所以最好避免使用它
    ,特别是如果你的代码需要可移植。使用这个
    技巧,你也可以结束无意的类型双关。如果一个
    的文件声明 i double 而不是 int ,C的
    类型不安全链接器可能不会发现不匹配。如果你使用的是64位 int double
    a机器,那么你甚至不会得到一个警告;在32位 int 和64位 double 的机器上
    ,你可能会得到一个
    关于不同大小的警告 - 链接器将使用最大的
    大小,正如Fortran程序将占用任何
    常见块的最大大小。



    信息性附录J中的C标准中提到这是一个普通的
    扩展:


    J。 5.11多个外部定义

    对于
    对象的标识符,可能有多个外部定义,有或没有明确使用关键字extern;如果
    定义不同意或者多个被初始化,则行为是
    undefined(6.9.2)。






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



    prog2.h



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



    prog2.c



      #includeprog2.h
    #include< stdio.h>

    int main(void)
    {
    inc();
    put();
    dec();
    put();
    dec();
    put();

    $ / code>




    • prog2 使用 prog2.c file10.c file11.c file12.c prog2.h






    警告



    正如这里的评论所述,在我对类似
    问题的回答中,对全局变量使用多个
    定义会导致未定义的行为,这是标准方式任何事情都可能发生的

    可能发生的一件事是该程序的行为与
    期望的一样; J.5.11大概说,你可能更幸运
    比你应得的。
    但是一个依赖于外部变量
    的多重定义的程序(带或不带明确的extern关键字)并不是一个严格符合
    标准的程序,并且不能保证在任何地方都能正常工作。
    等同:它包含一个可能会或可能不会显示的错误。



    违反准则



    faulty_header.h



      int some_var; / *不要在标题中这样做! * / 

    注1:如果标题定义的变量没有 extern 关键字,
    ,那么每个包含头文件的文件都会创建一个变量的临时定义


    broken_header.h



      int some_var = 13; / *程序中只有一个源文件可以使用这个* / 

    注2:并初始化变量,那么在一个给定的程序中只有
    一个源文件可以使用该头文件。



    seldom_correct.h



      static int hidden_​​global = 3; / *每个源文件都有自己的副本* / 

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



    如果变量实际上是一个复杂的数组,例如,这可能导致
    导致代码的极端重复。它可以,非常偶然,是一个
    明智的方式来达到一些效果,但这是相当不寻常的。





    总结



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


    声明和定义函数也有类似的问题 - 类似的
    规则适用。但问题是关于变量的具体问题,所以我只有
    只保留了变量的答案。



    (完整的程序使用函数,所以函数声明有$在
    前面的函数声明中使用关键字 extern 来匹配 extern
    头文件中的变量声明前面,很多人不喜欢在函数前面使用 extern ;
    编译器不关心 - 最终,只要
    你是一致的,我也不会这样做。)



    原始回答结束



    如果您不是经验丰富的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 
    #includefile3a.h/ *已定义变量 - 但未初始化* /
    #includeprog3.h

    int增量(void){return global_variable ++; }



    file2a.c



      #includefile3a.h
    #includeprog3.h
    #include< stdio.h>

    void use_it(void)
    {
    printf(全局变量:%d \ n,global_variable ++);
    }






    下一个两个文件完成 prog3 的源代码:



    prog3.h



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



    prog3.c



      #includefile3a.h
    #includeprog3.h
    #include< stdio.h>

    int main(void)
    {
    use_it();
    global_variable + = 19;
    use_it();
    printf(Increment:%d \\\
    ,increment());
    返回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 块,修复由
    标识的错误 Denis Kniazhev

    file1b.c



      #define DEFINE_VARIABLES 
    #includefile3b.h/ *现在定义和初始化的变量* /
    #includeprog4.h

    int increment(void){return global_variable ++ ; }
    int oddball_value(void){return oddball_struct.a + oddball_struct.b; }



    file2b.c



      #includefile3b.h
    #includeprog4.h
    #include< stdio.h>

    void use_them(void)
    {
    printf(全局变量:%d \ n,global_variable ++);
    oddball_struct.a + = global_variable;
    oddball_struct.b - = global_variable / 2;
    }

    显然,oddball结构的代码并不是你通常所用的
    写,但它说明了这一点。第二个
    调用 INITIALIZER 的第一个参数是 {41 ,其余参数
    (在这个例子中是单数)是 43} 。如果没有C99或类似的支持
    用于宏的可变参数列表,那么需要
    的初始化程序包含逗号是非常有问题的。


    正确的标题 file3b.h 包含(而不是 fileba.h )每
    Denis Kniazhev






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



    prog4.h h3>

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



    prog4.c



      #includefile3b.h
    #includeprog4.h
    #include< stdio.h>

    int main(void)
    {
    use_them();
    global_variable + = 19;
    use_them();
    printf(Increment:%d \\\
    ,increment());
    printf(Oddball:%d \\\
    ,oddball_value());
    返回0;




    • prog4 使用 prog4.c file1b.c file2b.c prog4.h file3b.h






    标题警卫



    任何标题都应该受到保护,因此类型
    定义(枚举,结构或联合类型或typedef通常)不
    会导致问题。标准技术是将
    头部的主体封装在一个头部警卫中,例如:

      #ifndef FILE3B_H_INCLUDED 
    #define FILE3B_H_INCLUDED

    ...标题内容...

    #endif / * FILE3B_H_INCLUDED * /

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

    此外,它会启动因为在包含 file3b.h 之前可能包含 file4b.h
    来生成定义,但是 file3b.h 中正常的
    标头会阻止标题重新包含。



    您需要为
    声明最多包含一次 file3b.h 的主体,并且最多一次包含定义,但您可能需要同时包含
    一个单一的翻译单元(TU - 一个源文件和
    的标题组合)。
    $ b

    然而,这可以通过一个不太无理的限制来完成。
    让我们介绍一组新的文件名:


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

    • file1c.h 来定义类型(特别是, struct oddball oddball_struct 类型)。

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

    • file3c.c 定义了全局变量。 b $ b
    • file4c.c 它简单地使用全局变量。
    • file5c.c 这表明你可以声明并定义全局变量。
    • file6c.c 它表明你可以定义然后(试图)声明全局变量。

      $ b $ p

      在这些例子中, file5c.c file6c.c 直接包含头部
      file2c.h 几次,但这是证明
      机制起作用的最简单方法。这意味着如果标题间接包含
      两次,它也是安全的。



      这个工作的限制是:


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


      2. 定义宏DEFINE_VARIABLES。
      3. 定义或声明变量的头文件具有程式化的内容。
      4. >

        external.h



          / * 
        **此头必须不包含标题保护(例如< assert.h>绝对不能)。
        **每次调用时,都会根据当前是否定义了宏DEFINE_VARIABLES来重新定义宏EXTERN,INITIALIZE
        **。
        * /
        #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



          / *标准序言* / 
        #if defined(DEFINE_VARIABLES)&& !defined(FILE2C_H_DEFINITIONS)
        #undef FILE2C_H_INCLUDED
        #endif

        #ifndef FILE2C_H_INCLUDED
        #define FILE2C_H_INCLUDED

        #includeexternal.h / *支持宏EXTERN,INITIALIZE * /
        #includefile1c.h/ * struct oddball的类型定义* /

        #if!defined(DEFINE_VARIABLES)|| !定义(FILE2C_H_DEFINITIONS)

        / *全局变量声明/定义* /
        EXTERN int global_variable INITIALIZE(37);
        EXTERN struct oddball oddball_struct INITIALIZE({41,43});

        #endif / *!DEFINE_VARIABLES || !FILE2C_H_DEFINITIONS * /

        / *标准结语* /
        #ifdef DEFINE_VARIABLES
        #define FILE2C_H_DEFINITIONS
        #endif / * DEFINE_VARIABLES * /

        #endif / * FILE2C_H_INCLUDED * /



        file3c.c



          #define DEFINE_VARIABLES 
        #includefile2c.h/ *现在定义和初始化变量* /

        int increment(void) {return global_variable ++; }
        int oddball_value(void){return oddball_struct.a + oddball_struct.b; }



        file4c.c



          #includefile2c.h
        #include< stdio.h>

        void use_them(void)
        {
        printf(全局变量:%d \ n,global_variable ++);
        oddball_struct.a + = global_variable;
        oddball_struct.b - = global_variable / 2;
        }



        file5c.c



        < pre $ #includefile2c.h/ *声明变量* /

        #define DEFINE_VARIABLES
        #includefile2c.h/ *现在变量定义并初始化* /

        int increment(void){return global_variable ++; }
        int oddball_value(void){return oddball_struct.a + oddball_struct.b; }



        file6c.c



          #define DEFINE_VARIABLES 
        #includefile2c.h/ *现在定义和初始化变量* /

        #includefile2c.h/ *声明变量* /

        int increment(void){return global_variable ++; }
        int oddball_value(void){return oddball_struct.a + oddball_struct.b; }






        下一个源文件完成为 prog5 prog6 prog7



        prog5.c



         #包括file2c.h
        #include< stdio.h>

        int main(void)
        {
        use_them();
        global_variable + = 19;
        use_them();
        printf(Increment:%d \\\
        ,increment());
        printf(Oddball:%d \\\
        ,oddball_value());
        返回0;

        $ / code>




        • 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 )来定义变量。除了不这样做之外,没有任何
        的简单方法。



        您可以通过修改<$ c来部分解决该问题$ c> file2c.h 转换为
        file2d.h

        file2d.h



          / *标准序言* / 
        #if defined(DEFINE_VARIABLES)&& !defined(FILE2D_H_DEFINITIONS)
        #undef FILE2D_H_INCLUDED
        #endif

        #ifndef FILE2D_H_INCLUDED
        #define FILE2D_H_INCLUDED

        #includeexternal.h / *支持宏EXTERN,INITIALIZE * /
        #includefile1c.h/ * struct oddball的类型定义* /

        #if!defined(DEFINE_VARIABLES)|| !定义(FILE2D_H_DEFINITIONS)

        / *全局变量声明/定义* /
        EXTERN int global_variable INITIALIZE(37);
        EXTERN struct oddball oddball_struct INITIALIZE({41,43});

        #endif / *!DEFINE_VARIABLES || !FILE2D_H_DEFINITIONS * /

        / *标准结语* /
        #ifdef DEFINE_VARIABLES
        #define FILE2D_H_DEFINITIONS
        #undef DEFINE_VARIABLES
        #endif / * DEFINE_VARIABLES * /

        #endif / * FILE2D_H_INCLUDED * /

        问题变成'should the头文件包含 #undef DEFINE_VARIABLES ?'
        如果您从头文件中省略了这些元素,并用
        #define #undef

          #define DEFINE_VARIABLES 
        #includefile2c.h
        #undef DEFINE_VARIABLES

        代码(所以头文件不会改变
        DEFINE_VARIABLES 的值),那么你应该干净。
        必须记住要写出额外的行,这真是令人讨厌。替代方案可能是:

        pre $ #define HEADER_DEFINING_VARIABLESfile2c.h
        #includeexterndef.h



        externdef.h



          / * 
        **这个头文件不能包含头文件(像< assert.h>绝对不能)。
        **每次包含它时,宏HEADER_DEFINING_VARIABLES应该以
        **的名称(在引号中 - 或者可能是尖括号中)定义
        **要包含的头文件定义变量宏
        ** DEFINE_VARIABLES被定义时。另请参见:external.h(使用
        ** DEFINE_VARIABLES并适当地定义宏EXTERN和INITIALIZE
        **)。
        **
        ** #define HEADER_DEFINING_VARIABLESfile2c.h
        ** #includeexterndef.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 )。 b
        $ b

        file7c.c



          / *声明变量* / 
        #include file2d.h

        / *定义变量* /
        #define HEADER_DEFINING_VARIABLESfile2d.h
        #includeexterndef.h

        / *声明变量 - 再次* /
        #includefile2d.h

        / *定义变量 - 再次* /
        #define HEADER_DEFINING_VARIABLESfile2d.h
        #includeexterndef.h

        int increment(void){return global_variable ++; }
        int oddball_value(void){return oddball_struct.a + oddball_struct.b; }



        file8c.h



          / *标准序言* / 
        #if defined(DEFINE_VARIABLES)&& !defined(FILE8C_H_DEFINITIONS)
        #undef FILE8C_H_INCLUDED
        #endif

        #ifndef FILE8C_H_INCLUDED
        #define FILE8C_H_INCLUDED

        #includeexternal.h / *支持宏EXTERN,INITIALIZE * /
        #includefile2d.h/ * struct oddball * /

        #if!defined(DEFINE_VARIABLES)|| !定义(FILE8C_H_DEFINITIONS)

        / *全局变量声明/定义* /
        EXTERN struct oddball another INITIALIZE({14,34});

        #endif / *!DEFINE_VARIABLES || !FILE8C_H_DEFINITIONS * /

        / *标准结语* /
        #ifdef DEFINE_VARIABLES
        #define FILE8C_H_DEFINITIONS
        #endif / * DEFINE_VARIABLES * /

        #endif / * FILE8C_H_INCLUDED * /



        file8c.c



          / *定义变量* / 
        #define HEADER_DEFINING_VARIABLESfile2d.h
        #includeexterndef.h

        / *定义变量* /
        #define HEADER_DEFINING_VARIABLESfile8c.h
        #includeexterndef.h

        int increment(void){return global_variable ++; }
        int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }






        The next two files complete the source for prog8 and 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 uses prog8.c, file7c.c, file9c.c.

        • prog9 uses prog8.c, file8c.c, file9c.c.






        However, the problems are relatively unlikely to occur in practice,
        especially if you take the standard advice to



        Avoid global variables






        Does this exposition miss anything?



        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.



        NB These are toy programs with just barely enough code to make them
        marginally interesting. There is repetition within the examples that
        could be removed, but isn’t to simplify the pedagogical explanation.
        (For example: the difference between prog5.c and prog8.c is the name
        of one of the headers that are included. It would be possible to
        reorganize the code so that the main() function was not repeated, but
        it would conceal more than it revealed.)


        I know that global variables in C sometimes have the extern keyword. What is an extern variable? What is the declaration like? What is its scope?

        This is related to sharing variables across source files, but how does that work precisely? Where do I use extern?

        解决方案

        Using extern is only of relevance when the program you're building consists of multiple source files linked together, where some of the variables defined, for example, in source file file1.c need to be referenced in other source files, such as file2.c.

        It is important to understand the difference between defining a variable and declaring a variable:

        • A variable is declared when the compiler is informed that a variable exists (and this is its type); it does not allocate the storage for the variable at that point.
        • A variable is defined when the compiler allocates the storage for the variable.

        You may declare a variable multiple times (though once is sufficient); you may only define it once within a given scope. A variable definition is also a declaration, but not all variable declarations are definitions.

        Best way to declare and define global variables

        Although there are other ways of doing it, the clean, reliable way to declare and define global variables is to use a header file file3.h to contain an extern declaration of the variable. The header is included by the one source file that defines the variable and by all the source files that reference the variable. For each program, one source file (and only one source file) defines the variable. Similarly, one header file (and only one header file) should declare the variable.

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

        That's the best way to use them.


        The next two files complete the source for prog1:

        Notice that I use the keyword extern in front of function declarations in headers (as shown in prog1.h, for example) to match the extern in front of variable declarations in headers. Many people prefer not to use extern in front of functions; the compiler doesn't care — and ultimately, neither do I as long as you're consistent.

        prog1.h

        extern void use_it(void);   // "extern" is optional here; see note above
        extern int increment(void); // "extern" is optional here; see note above
        

        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 uses prog1.c, file1.c, file2.c, file3.h and prog1.h.

        Guidelines

        Rules to be broken by experts only, and only with good reason:

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

        If you're not an experienced C programmer, you could (and perhaps should) stop reading here.

        The source code and text of this answer are available in my SOQ (Stack Overflow Questions) repository on GitHub in the src/so-0143-3204 sub-directory.

        Not so good way to define global variables

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

        This technique does not conform to the letter of the C standard and the 'one definition rule', but the C standard lists it as a common variation on its one definition rule. Because this technique is not always supported, it is best to avoid using it, especially if your code needs to be portable. Using this technique, you can also end up with unintentional type punning. If one of the files declared i as a double instead of as an int, C's type-unsafe linkers probably would not spot the mismatch. If you're on a machine with 64-bit int and double, you'd not even get a warning; on a machine with 32-bit int and 64-bit double, you'd probably get a warning about the different sizes — the linker would use the largest size, exactly as a Fortran program would take the largest size of any common blocks.

        This is mentioned in the C standard in informative Annex J as a common extension:

        J.5.11 Multiple external definitions

        There may be more than one external definition for the identifier of an object, with or without the explicit use of the keyword extern; if the definitions disagree, or more than one is initialized, the behavior is undefined (6.9.2).


        The next two files complete the source for 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 uses prog2.c, file10.c, file11.c, file12.c, prog2.h.

        Warning

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

        Violating the guidelines

        faulty_header.h

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

        Note 1: if the header defines the variable without the extern keyword, then each file that includes the header creates a tentative definition of the variable.

        broken_header.h

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

        Note 2: if the header defines and initializes the variable, then only one source file in a given program can use the header.

        seldom_correct.h

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

        Note 3: if the header defines a static variable (with or without initialization), then each source file ends up with its own private version of the 'global' variable.

        If the variable is actually a complex array, for example, this can lead to extreme duplication of code. It can, very occasionally, be a sensible way to achieve some effect, but that is rather unusual.


        Summary

        Use the header technique I showed first. It works reliably and everywhere. Note, in particular, that the header declaring the global_variable is included in every file that uses it — including the one that defines it. This ensures that everything is self-consistent.

        Similar concerns arise with declaring and defining functions — analogous rules apply. But the question was about variables specifically, so I've kept the answer to variables only.

        (The complete programs use functions, so function declarations have crept in. I use the keyword extern in front of function declarations in headers to match the extern in front of variable declarations in headers. Many people prefer not to use extern in front of functions; the compiler doesn't care — and ultimately, neither do I as long as you're consistent.)

        End of Original Answer

        If you're not an experienced C programmer, you probably should stop reading here.


        Late Major Addition

        Avoiding Code Duplication

        One concern that is sometimes (and legitimately) raised about the 'declarations in headers, definitions in source' mechanism described here is that there are two files to be kept synchronized — the header and the source. This is usually followed up with an observation that a macro can be used so that the header serves double duty — normally declaring the variables, but when a specific macro is set before the header is included, it defines the variables instead.

        Another concern can be that the variables need to be defined in each of a number of 'main programs'. This is normally a spurious concern; you can simply introduce a C source file to define the variables and link the object file produced with each of the programs.

        A typical scheme works like this, using the original global variable illustrated in 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++);
        }
        


        The next two files complete the source for 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 uses prog3.c, file1a.c, file2a.c, file3a.h, prog3.h.

        Variable initialization

        The problem with this scheme as shown is that it does not provide for initialization of the global variable. With C99 or C11 and variable argument lists for macros, you could define a macro to support initialization too. (With C89 and no support for variable argument lists in macros, there is no easy way to handle arbitrarily long initializers.)

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

        Reverse contents of #if and #else blocks, fixing bug identified by 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 <stdio.h>
        
        void use_them(void)
        {
            printf("Global variable: %d\n", global_variable++);
            oddball_struct.a += global_variable;
            oddball_struct.b -= global_variable / 2;
        }
        

        Clearly, the code for the oddball structure is not what you'd normally write, but it illustrates the point. The first argument to the second invocation of INITIALIZER is { 41 and the remaining argument (singular in this example) is 43 }. Without C99 or similar support for variable argument lists for macros, initializers that need to contain commas are very problematic.

        Correct header file3b.h included (instead of fileba.h) per Denis Kniazhev


        The next two files complete the source for 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 uses prog4.c, file1b.c, file2b.c, prog4.h, file3b.h.

        Header Guards

        Any header should be protected against reinclusion, so that type definitions (enum, struct or union types, or typedefs generally) do not cause problems. The standard technique is to wrap the body of the header in a header guard such as:

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

        The header might be included twice indirectly. For example, if file4b.h includes file3b.h for a type definition that isn't shown, and file1b.c needs to use both header file4b.h and file3b.h, then you have some more tricky issues to resolve. Clearly, you might revise the header list to include just file4b.h. However, you might not be aware of the internal dependencies — and the code should, ideally, continue to work.

        Further, it starts to get tricky because you might include file4b.h before including file3b.h to generate the definitions, but the normal header guards on file3b.h would prevent the header being reincluded.

        So, you need to include the body of file3b.h at most once for declarations, and at most once for definitions, but you might need both in a single translation unit (TU — a combination of a source file and the headers it uses).

        Multiple inclusion with variable definitions

        However, it can be done subject to a not too unreasonable constraint. Let's introduce a new set of file names:

        • external.h for the EXTERN macro definitions, etc.
        • file1c.h to define types (notably, struct oddball, the type of oddball_struct).
        • file2c.h to define or declare the global variables.
        • file3c.c which defines the global variables.
        • file4c.c which simply uses the global variables.
        • file5c.c which shows that you can declare and then define the global variables.
        • file6c.c which shows that you can define and then (attempt to) declare the global variables.

        In these examples, file5c.c and file6c.c directly include the header file2c.h several times, but that is the simplest way to show that the mechanism works. It means that if the header was indirectly included twice, it would also be safe.

        The restrictions for this to work are:

        1. The header defining or declaring the global variables may not itself define any types.
        2. Immediately before you include a header that should define variables, you define the macro DEFINE_VARIABLES.
        3. The header defining or declaring the variables has stylized contents.

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


        The next source file completes the source (provides a main program) for prog5, prog6 and 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 uses prog5.c, file3c.c, file4c.c, file1c.h, file2c.h, external.h.
        • prog6 uses prog5.c, file5c.c, file4c.c, file1c.h, file2c.h, external.h.
        • prog7 uses prog5.c, file6c.c, file4c.c, file1c.h, file2c.h, external.h.

        This scheme avoids most problems. You only run into a problem if a header that defines variables (such as file2c.h) is included by another header (say file7c.h) that defines variables. There isn't an easy way around that other than "don't do it".

        You can partially work around the problem by revising file2c.h into 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 */
        

        The issue becomes 'should the header include #undef DEFINE_VARIABLES?' If you omit that from the header and wrap any defining invocation with #define and #undef:

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

        in the source code (so the headers never alter the value of DEFINE_VARIABLES), then you should be clean. It is just a nuisance to have to remember to write the the extra line. An alternative might be:

        #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 */
        

        This is getting a tad convoluted, but seems to be secure (using the file2d.h, with no #undef DEFINE_VARIABLES in the 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; }
        

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


        The next two files complete the source for prog8 and 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 uses prog8.c, file7c.c, file9c.c.
        • prog9 uses prog8.c, file8c.c, file9c.c.

        However, the problems are relatively unlikely to occur in practice, especially if you take the standard advice to

        Avoid global variables


        Does this exposition miss anything?

        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.

        NB These are toy programs with just barely enough code to make them marginally interesting. There is repetition within the examples that could be removed, but isn't to simplify the pedagogical explanation. (For example: the difference between prog5.c and prog8.c is the name of one of the headers that are included. It would be possible to reorganize the code so that the main() function was not repeated, but it would conceal more than it revealed.)

        这篇关于如何使用extern在源文件之间共享变量?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆