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

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

问题描述

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

这是涉及到跨源文件的共享变量,但如何做这项工作precisely?我在哪里可以使用的extern


解决方案

使用的extern 仅在相关的程序时,你正在构建
包括连接在一起的多个源文件,其中一些
变量定义,例如,在源文件 file1.c中
在其他的源文件,如 file2.c中引用。

理解的确定的一个之间的区别是很重要的
变量的声明的变量


  • 系统变量的确定时,编译器分配的存储
    变量。

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

您可以声明一个变量多次(虽然一次就足够了);
你可能只给定范围内,一旦定义它。

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

虽然有这样做的其他方式,清洁,可靠的方法
声明和定义全局变量是使用头文件 file3.h
包含变量的的extern 声明的。标题是
包括定义该变量的一个源文件,并通过所有的
引用变量的源文件。对于每一个节目,一个源
文件(且只有一个源文件)定义变量。同样,人们
头文件(且只有一个头文件)应该声明变量。

file3.h

 的extern INT global_variable; / *变量声明* /

file1.c中

 的#includefile3.h/ *声明提供在这里* /
#包括prog1.h/ *函数声明* // *可变这里定义* /
INT global_variable = 37; / *定义对申报检查* /INT增量(无效){返回global_variable ++; }

file2.c中

 的#includefile3.h
#包括prog1.h
#包括LT&;&stdio.h中GT;无效use_it(无效)
{
    的printf(全局变量数:%d \\ n,global_variable ++);
}

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


接下来的两个文件完成对源 PROG1

prog1.h

 的extern无效use_it(无效);
EXTERN INT增量(无效);

prog1.c的

 的#includefile3.h
#包括prog1.h
#包括LT&;&stdio.h中GT;INT主要(无效)
{
    用它();
    global_variable + = 19;
    用它();
    的printf(增量数:%d \\ n,增量());
    返回0;
}


  • PROG1 使用 prog1.c的 file1.c中 file2.c中 file3.h prog1.h


指引

规则由专家被打破,并且只有很好的理由:


  • 头文件只包含的变量的extern 声明 - 从未
    静态或不合格的变量定义。

  • 对于任何给定的变量,只有一个头文件声明它(SPOT -
    真理的单点)。

  • 源文件决不会包含的extern 的变量声明 -
    源文件始终包含(唯一)头部声明他们。

  • 对于任何给定的变量,只有一个源文件定义变量,
    preferably初始化它。 (虽然没有必要
    明确初始化为零,它没有任何伤害,可以做一些很好的,
    因为只能有一个特定的初始化定义
    在程序中的全局变量)。

  • 定义变量还包括头源文件
    确保定义和声明是一致的。

  • 函数不应该需要使用的extern 来声明一个变量。

  • 避免全局变量尽可能 - 使用功能,而不是

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

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

对于一些(事实上,许多)C编译器,你可以逃脱什么
被称为变量的共同的定义了。 通用,在这里,是指
在Fortran语言用于源之间的共享变量技术
文件,使用(可能命名)的通用块。这里发生的是,
每一个数字文件的提供了一个试探性的定义
变量。只要不超过一个文件提供一个初始化
定义,则各种文件最终共享一个共同的单一
变量的定义:

file10.c

 的#includeprog2.hINT I; / *不要在便携式code *做到这一点/空公司(无效){我++; }

file11.c

 的#includeprog2.hINT I; / *不要在便携式code *做到这一点/无效DEC(无效){我 - ; }

file12.c

 的#includeprog2.h
#包括LT&;&stdio.h中GT;INT I = 9; / *不要在便携式code *做到这一点/无效认沽(无效){printf的(我=%d个\\ N,I); }

该技术不符合C标准和信
一个定义规则,但C标准列出了它作为一个共同变化
它一个定义规则。
因为这种技术不能支持,这是最好的,以避免
使用它,特别是如果你的code必须携带的。使用此
技术,你也可以与无意类型双关结束。如果一个人
申报文件的 I 双击,而不是作为一个 INT ,C的
类型不安全的链接器可能不会察觉的不匹配。如果你在
64位机器 INT 双击,你甚至没有得到一个警告;
32位的机器上 INT 和64位双击,你可能会得到一个
警告对不同尺寸 - 连接器将使用最大
大小,正是因为Fortran程序将采取任何的最大尺寸
公共块。

这是在资料附录J C标准提到的一个共同
延伸:


  

J.5.11多重外部定义


  
  

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



接下来的两个文件完成对源 PROG2

prog2.h

 的extern无效DEC(无效);
EXTERN无效认沽(无效);
EXTERN无效INC(无效);

prog2.c

 的#includeprog2.h
#包括LT&;&stdio.h中GT;INT主要(无效)
{
    增量();
    放();
    分解();
    放();
    分解();
    放();
}


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


警告

作为评论这里要注意,并在我的答案表示了类似的
<一href=\"http://stackoverflow.com/questions/1490693/tentative-definitions-in-c99-and-linking\">question,使用多个定义为一个全局变量导致
未定义的行为,这是说什么标准的方式
可能发生的。其中一个可能发生的事情是程序
行为像您期望的;和J.5.11说,大约,你可能会
幸运往往比你应得的。但是,这依赖于程序
一个外部变量的多个定义 - 带或不带
明确的extern关键字 - 不是一个严格符合程序,而不是
保证工作无处不在。等价的:它包含了一个错误,这
可以或可以不表现出本身

违反准则

faulty_header.h

  INT some_var; / *不要在标题做到这一点! * /

注1:如果标题定义变量,而不的extern 关键字,
那么每个包含头文件创建一个暂定定义
可变的。

broken_header.h

  INT some_var = 13; / *在程序只有一个源文件可以使用此* /

注2:如果头文件定义,仅初始化变量,则
一个源文件中给定的程序可以使用的报头

seldom_correct.h

 静态INT hidden_​​global = 3; / *每个源文件中获取自己的副本* /

注3:如果标题定义了一个静态变量(具有或不
初始化),那么每个源文件最终拥有自己的私人
版的全球性的变量。

如果变量实际上是一个复杂的阵列,例如,这可能导致
至code的极端重复。它可以,很偶然,是一个
明智的方法来达到一定的效果,但是这是相当不寻常。


摘要

使用头技术,我发现第一次。它的工作原理和可靠
到处。注意,尤其是,首标宣告
global_variable 包含在每个使用它的文件 - 包括
一个定义它。这保证了一切是自洽的

类似的担忧与产生的声明和定义功能 - 类似
规则适用。但问题是关于具体的变量,所以我
不停的答案只有变量

(完整的程序使用功能,所以函数声明有
蹑手蹑脚。我使用关键字的extern 在函数声明前
在标题匹配的extern 在变量声明前
头。很多人preFER不使用的extern 在前面的功能;
编译器不关心 - 最终,我也不只要
你是一致的。)

原来的答案结束

如果你不是一个有经验的C程序员,你应该停止阅读这里。


晚重大补充

避免code复制

其中值得关注的是,有时(和合法)提出了关于
声明在头文件,源定义的机制描述
这里要说的是有两个文件可以保持同步 - 页眉
和源极。这通常是跟进的观察,一个
可以用宏,以便头供应的双重任务 - 通常
声明的变量,但是,当特定宏之前设置
头在内,它定义变量来代替。

另一个关切可以是变量需要在每一个被定义
若干主程序。这通常是一个虚假的关注;您
能简单介绍一下C源文件来定义的变量和链接
与每个节目的产生的目标文件

一个典型的计划是这样的,使用原来的全局变量
所示 file3.h

file3a.h

  #IFDEF DEFINE_VARIABLES
#定义EXTERN / * *什么/
#其他
#定义EXTERN EXTERN
#ENDIF / * * DEFINE_VARIABLES /EXTERN INT global_variable;

file1a.c

 的#define DEFINE_VARIABLES
#包括file3a.h/ *变量定义 - 但不是初始化* /
#包括prog3.hINT增量(无效){返回global_variable ++; }

file2a.c

 的#includefile3a.h
#包括prog3.h
#包括LT&;&stdio.h中GT;无效use_it(无效)
{
    的printf(全局变量数:%d \\ n,global_variable ++);
}


接下来的两个文件完成对源 PROG3

prog3.h

 的extern无效use_it(无效);
EXTERN INT增量(无效);

prog3.c

 的#includefile3a.h
#包括prog3.h
#包括LT&;&stdio.h中GT;INT主要(无效)
{
    用它();
    global_variable + = 19;
    用它();
    的printf(增量数:%d \\ n,增量());
    返回0;
}


  • PROG3 使用 prog3.c file1a.c file2a.c file3a.h prog3.h


变量初始化

如图这个方案的问题在于,它没有提供用于
全局变量的初始化。随着C99或C11和可变参数
列出了宏,你可以定义一个宏来支持初始化了。
(随着C89以及在宏变量参数列表的支持,就没有
简单的方法来处理任意长的初始值。)

file3b.h

  #IFDEF DEFINE_VARIABLES
#定义EXTERN / * *什么/
#定义初始化(...)= __VA_ARGS__
#其他
#定义EXTERN EXTERN
#定义初始化(...)/ *不* /
#ENDIF / * * DEFINE_VARIABLES /EXTERN INT global_variable初始化(37);
EXTERN结构{int类型的; INT B: } oddball_struct初始化({41,43});

反向内容#如果的#else 块,修复错误鉴定
丹尼斯Kniazhev

file1b.c

 的#define DEFINE_VARIABLES
#包括file3b.h/ *变量现在定义并初始化* /
#包括prog4.hINT增量(无效){返回global_variable ++; }
INT oddball_value(无效){返回oddball_struct.a + oddball_struct.b; }

file2b.c

 的#includefile3b.h
#包括prog4.h
#包括LT&;&stdio.h中GT;无效use_them(无效)
{
    的printf(全局变量数:%d \\ n,global_variable ++);
    oddball_struct.a + = global_variable;
    oddball_struct.b - = global_variable / 2;
}

显然,code为古怪的结构是不是你通常
写,但它说明了这一点。第一个参数的第二个
初始化调用是 {41 ,其余参数
(在这个例子中奇)为 43} 。无C99或类似的支持
为宏变量参数列表,需要初始化
包含逗号是很成问题。

正确的标题 file3b.h 包括每人(而不是 fileba.h
丹尼斯Kniazhev


接下来的两个文件完成对源 PROG4

prog4.h

 的extern INT增量(无效);
EXTERN INT oddball_value(无效);
EXTERN无效use_them(无效);

prog4.c

 的#includefile3b.h
#包括prog4.h
#包括LT&;&stdio.h中GT;INT主要(无效)
{
    使用它们();
    global_variable + = 19;
    使用它们();
    的printf(增量数:%d \\ n,增量());
    的printf(穿着或行为古怪数:%d \\ n,oddball_value());
    返回0;
}


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


标题卫队

任何头应该对重新加入保护,使该类型
定义(枚举,结构或联合类型,或typedef的一般)不
引起问题。的标准技术是包装的主体
在头文件保护头,如:

 的#ifndef FILE3B_H_INCLUDED
#定义FILE3B_H_INCLUDED...标题的内容...#ENDIF / * * FILE3B_H_INCLUDED /

头可能会间接两次包括在内。例如,如果
file4b.h 包括 file3b.h 对于未显示的类型定义,
file1b.c 需要同时使用头文件 file4b.h file3b.h ,然后
你有一些更靠谱的问题来解决。很显然,你可能会修改
标题列表,只包含 file4b.h 。但是,你可能不
了解内部相关性 - 与code,最好应当
继续工作。

此外,它开始变得棘手,因为你可能包括 file4b.h
包括前 file3b.h 来生成的定义,但正常的
file3b.h 头卫士将$ P​​ $ pvent被重新纳入了头。

所以,你需要包括 file3b.h 正文最多一次
声明,最多一次定义,但您可能需要两个
在一个单一的翻译单元(TU - 源文件的组合,并且
标题它使用)。

多包容与变量定义

然而,这是可以做到受不太合理约束。
我们引进了一套新的文件名:


  • external.h 为EXTERN宏定义等。

  • file1c.h 来定义类型(值得注意的是,结构古怪的类型oddball_struct )。

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

  • file3c.c 定义的全局变量。

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

  • file5c.c 这表明你可以声明,然后定义的全局变量。

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

在这些例子中, file5c.c file6c.c 直接包含该头
file2c.h 几次,但是这是要表明,最简单的方法
机制的工作原理。这意味着,如果标题被间接地包含
两次,它也将是安全的。

对于这个工作的限制是:


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

  2. 您包括应该定义变量头的前一刻,
    你定义宏DEFINE_VARIABLES。

  3. 定义或声明的变量头已经程式化的内容。

external.h

  / *
**此头不能包含头卫士(如&LT;&ASSERT.H GT;不得)。
**每次调用时,它重新定义了宏EXTERN,请初始化
**基于宏观DEFINE_VARIABLES目前是否已确定。
* /
和#undef EXTERN
和#undef初始化#IFDEF DEFINE_VARIABLES
#定义EXTERN / * *什么/
#定义初始化(...)= __VA_ARGS__
#其他
#定义EXTERN EXTERN
#定义初始化(...)/ *不* /
#ENDIF / * * DEFINE_VARIABLES /

file1c.h

 的#ifndef FILE1C_H_INCLUDED
#定义FILE1C_H_INCLUDED古怪的结构
{
    int类型的;
    INT B:
};EXTERN无效use_them(无效);
EXTERN INT增量(无效);
EXTERN INT oddball_value(无效);#ENDIF / * * FILE1C_H_INCLUDED /

file2c.h

  / *标准序幕* /
#如果定义(DEFINE_VARIABLES)及和放大器; !定义(FILE2C_H_DEFINITIONS)
和#undef FILE2C_H_INCLUDED
#万一的#ifndef FILE2C_H_INCLUDED
#定义FILE2C_H_INCLUDED#包括external.h/ *支持宏EXTERN,请初始化* /
#包括file1c.h/ *类型定义结构古怪* /#如果!定义(DEFINE_VARIABLES)|| !定义(FILE2C_H_DEFINITIONS)/ *全局变量声明/定义* /
EXTERN INT global_variable初始化(37);
EXTERN结构古怪oddball_struct初始化({41,43});#ENDIF / *!DEFINE_VARIABLES || !FILE2C_H_DEFINITIONS * // *标准结尾* /
#IFDEF DEFINE_VARIABLES
#定义FILE2C_H_DEFINITIONS
#ENDIF / * * DEFINE_VARIABLES /#ENDIF / * * FILE2C_H_INCLUDED /

file3c.c

 的#define DEFINE_VARIABLES
#包括file2c.h/ *变量现在定义并初始化* /INT增量(无效){返回global_variable ++; }
INT oddball_value(无效){返回oddball_struct.a + oddball_struct.b; }

file4c.c

 的#includefile2c.h
#包括LT&;&stdio.h中GT;无效use_them(无效)
{
    的printf(全局变量数:%d \\ n,global_variable ++);
    oddball_struct.a + = global_variable;
    oddball_struct.b - = global_variable / 2;
}

file5c.c

 的#includefile2c.h/ *声明变量* /#定义DEFINE_VARIABLES
#包括file2c.h/ *变量现在定义并初始化* /INT增量(无效){返回global_variable ++; }
INT oddball_value(无效){返回oddball_struct.a + oddball_struct.b; }

file6c.c

 的#define DEFINE_VARIABLES
#包括file2c.h/ *变量现在定义并初始化* /#包括file2c.h/ *声明变量* /INT增量(无效){返回global_variable ++; }
INT oddball_value(无效){返回oddball_struct.a + oddball_struct.b; }


下一个源文件完成源(提供了一个主程序)为 prog5 prog6 prog7

prog5.c

 的#includefile2c.h
#包括LT&;&stdio.h中GT;INT主要(无效)
{
    使用它们();
    global_variable + = 19;
    使用它们();
    的printf(增量数:%d \\ n,增量());
    的printf(穿着或行为古怪数:%d \\ n,oddball_value());
    返回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

  / *标准序幕* /
#如果定义(DEFINE_VARIABLES)及和放大器; !定义(FILE2D_H_DEFINITIONS)
和#undef FILE2D_H_INCLUDED
#万一的#ifndef FILE2D_H_INCLUDED
#定义FILE2D_H_INCLUDED#包括external.h/ *支持宏EXTERN,请初始化* /
#包括file1c.h/ *类型定义结构古怪* /#如果!定义(DEFINE_VARIABLES)|| !定义(FILE2D_H_DEFINITIONS)/ *全局变量声明/定义* /
EXTERN INT global_variable初始化(37);
EXTERN结构古怪oddball_struct初始化({41,43});#ENDIF / *!DEFINE_VARIABLES || !FILE2D_H_DEFINITIONS * // *标准结尾* /
#IFDEF DEFINE_VARIABLES
#定义FILE2D_H_DEFINITIONS
和#undef DEFINE_VARIABLES
#ENDIF / * * DEFINE_VARIABLES /#ENDIF / * * FILE2D_H_INCLUDED /

问题变成'应头包括和#undef DEFINE_VARIABLES
如果省略了从头部,敷任何定义调用
的#define 和#undef

 的#define DEFINE_VARIABLES
#包括file2c.h
和#undef DEFINE_VARIABLES

在源$ C ​​$ C(所以头从来没有改变的价值
DEFINE_VARIABLES ),那么你就应该清洁。这仅仅是一个滋扰
要记得写多余的线。另一种可能是:

 的#define HEADER_DEFINING_VARIABLESfile2c.h
#包括externdef.h

externdef.h

  / *
**此头不能包含头卫士(如&LT;&ASSERT.H GT;不得)。
**每次它被包含,宏HEADER_DEFINING_VARIABLES应
**的名称来定义(在引号 - 或可能尖括号)的
**被包括在报头中定义的变量,当宏
** DEFINE_VARIABLES定义。参见:external.h(使用
** DEFINE_VARIABLES并定义宏EXTERN和初始化
**适当)。
**
**的#define HEADER_DEFINING_VARIABLESfile2c.h
**的#includeexterndef.h
* /#如果定义(HEADER_DEFINING_VARIABLES)
#定义DEFINE_VARIABLES
的#include HEADER_DEFINING_VARIABLES
和#undef DEFINE_VARIABLES
和#undef HEADER_DEFINING_VARIABLES
#ENDIF / * * HEADER_DEFINING_VARIABLES /

这是得到一点点绕口,但似乎是安全的(使用
file2d.h ,没有和#undef DEFINE_VARIABLES file2d.h )。

file7c.c

  / *声明变量* /
#包括file2d.h/ *定义变量* /
#定义HEADER_DEFINING_VARIABLESfile2d.h
#包括externdef.h/ *声明变量 - 再* /
#包括file2d.h/ *定义变量 - 再* /
#定义HEADER_DEFINING_VARIABLESfile2d.h
#包括externdef.hINT增量(无效){返回global_variable ++; }
INT oddball_value(无效){返回oddball_struct.a + oddball_struct.b; }

file8c.h

  / *标准序幕* /
#如果定义(DEFINE_VARIABLES)及和放大器; !定义(FILE8C_H_DEFINITIONS)
和#undef FILE8C_H_INCLUDED
#万一的#ifndef FILE8C_H_INCLUDED
#定义FILE8C_H_INCLUDED#包括external.h/ *支持宏EXTERN,请初始化* /
#包括file2d.h/ *结构古怪* /#如果!定义(DEFINE_VARIABLES)|| !定义(FILE8C_H_DEFINITIONS)/ *全局变量声明/定义* /
EXTERN结构古怪另一个初始化({14,34});#ENDIF / *!DEFINE_VARIABLES || !FILE8C_H_DEFINITIONS * // *标准结尾* /
#IFDEF DEFINE_VARIABLES
#定义FILE8C_H_DEFINITIONS
#ENDIF / * * DEFINE_VARIABLES /#ENDIF / * * FILE8C_H_INCLUDED /

file8c.c

  / *定义变量* /
#定义HEADER_DEFINING_VARIABLESfile2d.h
#包括externdef.h/ *定义变量* /
#定义HEADER_DEFINING_VARIABLESfile8c.h
#包括externdef.hINT增量(无效){返回global_variable ++; }
INT oddball_value(无效){返回oddball_struct.a + oddball_struct.b; }


接下来的两个文件的完整的源 prog8 prog9

prog8.c

 的#includefile2d.h
#包括LT&;&stdio.h中GT;INT主要(无效)
{
    使用它们();
    global_variable + = 19;
    使用它们();
    的printf(增量数:%d \\ n,增量());
    的printf(穿着或行为古怪数:%d \\ n,oddball_value());
    返回0;
}

file9c.c

 的#includefile2d.h
#包括LT&;&stdio.h中GT;无效use_them(无效)
{
    的printf(全局变量数:%d \\ n,global_variable ++);
    oddball_struct.a + = global_variable;
    oddball_struct.b - = global_variable / 2;
}


  • prog8 使用 prog8.c file7c.c file9c.c

  • prog9 使用 prog8.c file8c.c file9c.c


但是,存在的问题是相对不太可能在实践中发生,
特别是如果你把标准的意见,以

避免全局变量


这是否博览会错过什么?

忏悔的:这里列出的是避免重复code计划
开发因为这个问题影响到一些code对我的工作(但不拥有)
并且与在第一部分中概述的方案的琐碎关注
答案。但是,原来的计划让你只有两个
地方要修改,以保持变量定义和声明
同步的,这是一大进步在具有exernal变量
声明散布在code基(这真的很重要
当有成千上万的文件计)。然而,code在
随着名称的文件 fileNc。[CH] (加上 external.h externdef.h
表明,它可向工作。显然,它不会很难
创建一个标题生成脚本来给你模板标准化
一个变量定义和声明的头文件。

NB 的只勉强够code这些都是玩具的方案,使他们
稍微有趣。还有就是例子中的重复
可以被除去,但并不以简化教学解释。
(例如:之间的差异 prog5.c prog8.c 是名称
的所包含的报头中的一个。这将是可能的
重组code,这样不重复的main()的功能,但
它会掩盖超过了它显露出来。)

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 defined when the compiler allocates the storage for the 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.

You may declare a variable multiple times (though once is sufficient); you may only define it once within a given scope.

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:

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

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共享源文件之间的变量用C?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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