體系結構計劃
外部鏈接的靜態變量具有文件作用域、外部鏈接和靜態存儲期。該類別有時稱為外部存儲類別(external storage class),屬于該類別的變量稱為外部變量(external variable)。把變量的定義性聲明(defining declaration)放在所有函數的外面便創建了外部變量。當然,為了指出該函數使用了外部變量,可以在函數中用關鍵字extern再次聲明。如果一個源代碼文件使用的外部變量定義在另一個源代碼文件中,則必須用extern在該文件中聲明該變量。如下所示:
int Errupt; /* externally defined variable */
double Up[100]; /* externally defined array */
extern char Coal; /* mandatory declaration if */
/* Coal defined in another file */
void next(void);
int main(void)
{
extern int Errupt; /* optional declaration */
extern double Up[]; /* optional declaration */
...
}
void next(void)
{
...
}
注意,在main()中聲明Up數組時(這是可選的聲明)不用指明數組大小,因為第1次聲明已經提供了數組大小信息。main()中的兩條extern聲明完全可以省略,因為外部變量具有文件作用域,所以Errupt和Up從聲明處到文件結尾都可見。它們出現在那里,僅為了說明main()函數要使用這兩個變量。如果省略掉函數中的extern關鍵字,相當于創建了一個自動變量。去掉下面聲明中的extern:
extern int Errupt;
便成為:
int Errupt;
這使得編譯器在main()中創建了一個名為Errupt的自動變量。它是一個獨立的局部變量,與原來的外部變量Errupt不同。該局部變量僅main()中可見,但是外部變量Errupt對于該文件的其他函數(如next())也可見。簡而言之,在執行塊中的語句時,塊作用域中的變量將“隱藏”文件作用域中的同名變量。如果不得已要使用與外部變量同名的局部變量,可以在局部變量的聲明中使用auto存儲類別說明符明確表達這種意圖。外部變量具有靜態存儲期。因此,無論程序執行到main()、next()還是其他函數,數組Up及其值都一直存在。下面3個示例演示了外部和自動變量的一些使用情況。示例1中有一個外部變量Hocus。該變量對main()和magic()均可見。
/* Example 1 */
int Hocus;
int magic();
int main(void)
{
extern int Hocus; // Hocus declared external
...
}
int magic()
{
extern int Hocus; // same Hocus as above
...
}
示例2中有一個外部變量Hocus,對兩個函數均可見。這次,在默認情況下對magic()可見。
/* Example 2 */
int Hocus;
int magic();
int main(void)
{
extern int Hocus; // Hocus declared external
...
}
int magic()
{
// Hocus not declared but is known
...
}
在示例3中,創建了4個獨立的變量。main()中的Hocus變量默認是自動變量,屬于main()私有。magic()中的Hocus變量被顯式聲明為自動,只有magic()可用。外部變量Hocus對main()和magic()均不可見,但是對該文件中未創建局部Hocus變量的其他函數可見。最后,Pocus是外部變量,magic()可見,但是main()不可見,因為Pocus被聲明在main()后面。
/* Example 3 */
int Hocus;
int magic();
int main(void)
{
int Hocus; // Hocus declared, is auto by default
...
}
int Pocus;
int magic()
{
auto int Hocus; // local Hocus declared automatic
...
}
這3個示例演示了外部變量的作用域是:從聲明處到文件結尾。除此之外,還說明了外部變量的生命期。外部變量Hocus和Pocus在程序運行中一直存在,因為它們不受限于任何函數,不會在某個函數返回后就消失。
初始化外部變量
外部變量和自動變量類似,也可以被顯式初始化。與自動變量不同的是,如果未初始化外部變量,它們會被自動初始化為0。這一原則也適用于外部定義的數組元素。與自動變量的情況不同,只能使用常量表達式初始化文件作用域變量:
int x = 10; // ok, 10 is constant
int y = 3 + 20; // ok, a constant expression
size_t z = sizeof(int); // ok, a constant expression
int x2 = 2 * x; // not ok, x is a variable
(只要不是變長數組,sizeof表達式可被視為常量表達式。)
使用外部變量
下面來看一個使用外部變量的示例。假設有兩個函數main()和critic(),它們都要訪問變量units。可以把units聲明在這兩個函數的上面,如程序清單12.4所示(注意:該例的目的是演示外部變量的工作原理,并非它的典型用法)。
/* global.c -- uses an external variable */
#include <stdio.h>
int units = 0; /* an external variable */
void critic(void);
int main(void)
{
extern int units; /* an optional redeclaration */
printf("How many pounds to a firkin of butter?n");
scanf("%d", &units);
while ( units != 56)
critic();
printf("You must have looked it up!n");
return 0;
}
void critic(void)
{
/* optional redeclaration omitted */
printf("No luck, my friend. Try again.n");
scanf("%d", &units);
}
下面是該程序的輸出示例:
How many pounds to a firkin of butter?
14
No luck, my friend. Try again.
56
You must have looked it up!
(We did.)
注意,critic()是如何讀取units的第2個值的。當while循環結束時,main()也知道units的新值。所以main()函數和critic()都可以通過標識符units訪問相同的變量。用C的術語來描述是,units具有文件作用域、外部鏈接和靜態存儲期。
把units定義在所有函數定義外面(即外部),units便是一個外部變量,對units定義下面的所有函數均可見。因此,critics()可以直接使用units變量。
類似地,main()也可直接訪問units。但是,main()中確實有如下聲明:
extern int units;
本例中,以上聲明主要是為了指出該函數要使用這個外部變量。存儲類別說明符extern告訴編譯器,該函數中任何使用units的地方都引用同一個定義在函數外部的變量。再次強調,main()和critic()使用的都是外部定義的units。
外部名稱
C99和C11標準都要求編譯器識別局部標識符的前63個字符和外部標識符的前31個字符。這修訂了以前的標準,即編譯器識別局部標識符前31個字符和外部標識符前6個字符。你所用的編譯器可能還執行以前的規則。外部變量名比局部變量名的規則嚴格,是因為外部變量名還要遵循局部環境規則,所受的限制更多。
定義和聲明
下面進一步介紹定義變量和聲明變量的區別。考慮下面的例子:
int tern = 1; /* tern defined */
main()
{
external int tern; /* use a tern defined elsewhere */
這里,tern被聲明了兩次。第1次聲明為變量預留了存儲空間,該聲明構成了變量的定義。第2次聲明只告訴編譯器使用之前已創建的tern變量,所以這不是定義。第1次聲明被稱為定義式聲明(defining declaration),第2次聲明被稱為引用式聲明(referencing declaration)。關鍵字extern表明該聲明不是定義,因為它指示編譯器去別處查詢其定義。
假設這樣寫:
extern int tern;
int main(void)
{
編譯器會假設tern實際的定義在該程序的別處,也許在別的文件中。該聲明并不會引起分配存儲空間。因此,不要用關鍵字extern創建外部定義,只用它來引用現有的外部定義。
外部變量只能初始化一次,且必須在定義該變量時進行。假設有下面的代碼:
// file one.c
char permis = 'N';
...
// file two.c
extern char permis = 'Y'; /* error */
file_two中的聲明是錯誤的,因為file_one.c中的定義式聲明已經創建并初始化了permis。