外部变量
在B语言、C语言和一些其它派生的语言(如C++)中,外部变量即外部的变量。这并不是语言规范中直接明确的概念,因此含义可能有歧义。严格地,“外部”可以指变量名具有的外部链接(external linkage),据此外部变量指变量名具有外部链接的变量。其它理解导致外延与之具有一定差异,在下面的例子中注释。
注意“变量”的概念在这些语言中本身即具有一些差异。ISO C中没有作为名词的“变量”(variable)这一术语的正式定义,通常即指对象,而ISO C++规定变量通过对象或不是非静态数据成员的引用的声明引入,其中“对象”的概念和ISO C的基本兼容。除非另行说明,下文取其公共含义,即变量均指对象。变量名在此即为标识符(C++的名称外延比标识符更广,在此不考虑非标识符的情形)。
例子
下面的文件可以作为C或C++(源代码确保符合ISO C11和ISO C++11)的源文件,分别作为一个翻译单元,翻译后链接为一个程序。
文件1:
int GlobalVariable; // 同一个翻译单元中,在文件作用域(C)或全局命名空间作用域(C++)的首次声明的变量名隐式具有外部链接
void SomeFunction(void); // 不是函数定义的函数声明,因为明确指定了具体类型,在C语言中是函数原型
int main() {
GlobalVariable = 1;
SomeFunction();
}
文件2:
extern int GlobalVariable; // 通过关键字extern显式指定被声明的变量名具有外部链接
// static int GlobalVariable; // 若使用static关键字,可显式指定被声明的变量名具有内部链接,这样就不会指称翻译单元中声明的具有外部链接的对象,即使变量名相同
void SomeFunction(void) { // 函数定义
// int GlobalVariable; // 若在这里声明变量,则隐藏块作用域外部的变量名,这个变量没有链接,和块作用域外部的变量名不同
// extern int GlobalVariable; // 若在这里以extern声明同名变量,则重复声明之前的变量,链接取决于之前的声明,可能是或者不是外部链接
++GlobalVariable;
}
在每个翻译单元中,标识符都必须先被声明。这个例子里,变量 GlobalVariable 在文件1中被声明,这个声明同时是定义。为了在文件2中使同一个标识符指称相同实体,它必须被声明具有外部链接。
ISO C要求函数或对象的标识符若被使用则有且仅有一个外部定义,或未被使用时可以没有定义或具有一个定义,否则行为未定义。通常除非使用扩展(例如弱符号),实现(链接器)一般会有检查。
ISO C++规定合式(well-formed)必须在符合语法规则、可诊断语义规则的同时遵守One Definition Rule,其中多个翻译单元内的同一个实体必须具有唯一定义,这包括具有外部链接的变量。不管有多少个翻译单元,定义在整个程序中有且仅有一个。对于跨翻译单元的情形,如果违反此规定,通常会产生链接错误。
除了外部链接,“外部”可能被理解为“在块作用域外部”,见例子中SomeFunction函数定义中的注释。这里,“外部”变量不一定具有外部链接(比如,首次声明时显式使用了static关键字指定变量名具有内部链接,或者(C++)声明在具有内部链接的无名命名空间(unnamed namespace)内部)。由于这些微妙的歧义,通常不使用这种理解。
由于C语言中外部链接的对象被习惯性误称为全局变量,“外部”还可能会被误解为“全局”——严格地,这种理解的“外部”着眼于特定的源文件,指非当前翻译单元。但是事实上C语言本身并没有严格约定“全局”的含义。和C语言不同的是,C++存在严格意义上的全局变量,此处“全局”被正式定义为全局命名空间作用域。但是,这个概念的外延实际上和C++的“外部变量”不同:非全局命名作用域的变量的名称可以具有外部链接,这不是全局变量;而全局命名空间作用域中也可以直接使用static声明具有内部链接的全局变量。
此外,ISO C规定没有初值符且没有存储类指示符(storage specifier)或static指定的对象声明是tentative definition(ISO C++则明确禁止),和重复声明一起可能显著加剧上述理解的混乱。以下例子引用自ISO C11 6.9.2:
int i1 = 1; // definition, external linkage
static int i2 = 2; // definition, internal linkage
extern int i3 = 3; // definition, external linkage
int i4; // tentative definition, external linkage
static int i5; // tentative definition, internal linkage
int i1; // valid tentative definition, refers to previous
int i2; // 6.2.2 renders undefined, linkage disagreement
int i3; // valid tentative definition, refers to previous
int i4; // valid tentative definition, refers to previous
int i5; // 6.2.2 renders undefined, linkage disagreement
extern int i1; // refers to previous, whose linkage is external
extern int i2; // refers to previous, whose linkage is internal
extern int i3; // refers to previous, whose linkage is external
extern int i4; // refers to previous, whose linkage is external
extern int i5; // refers to previous, whose linkage is internal
若外延限制为以上理解外延的交集,即文件作用域(C)/全局命名空间作用域(C++)中首次使用extern或没有存储类指示符的声明引入的变量,则没有歧义。绝大多数情况下,“外部变量”都属于这个范畴。