外部變量
在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或沒有存儲類指示符的聲明引入的變量,則沒有歧義。絕大多數情況下,「外部變量」都屬於這個範疇。