在C和C++中,static都有兩種基本的含義,並且這兩種含義經常是互相有沖突的:
1) 在固定的地址上分配,也就是說對象是在一個特殊的靜態數據區上創建的,而不是每次
函數調用時在堆棧上產生的。這也是靜態存儲的概念。
2) 對一個特定的編譯單位來說是本地的(就像我們在後面將要看到的,這在C++中包括類
的范圍)。這裡static控制名字的可見性,所以這個名字在這個單元或類之外是不可見的。這也
描述了連接的概念,它決定連接器將看到哪些名字。
1.函數內部的靜態變量
通常,在函數體內定義一個變量時,編譯器使得每次函數調用時堆棧的指針向下移一個適
當的位置,為這些內部變量分配內存。如果這個變量有一個初始化表達式,那麼每當程序運行
到此處,初始化就被執行。
然而,有時想在兩次函數調用之間保留一個變量的值,我們可以通過定義一個全局變量來
實現這點,但這樣一來,這個變量就不僅僅受這個函數的控制。C和C++都允許在函數內部創
建一個static對象,這個對象將存儲在程序的靜態數據區中,而不是在堆棧中。這個對象只在
函數第一次調用時初始化一次,以後它將在兩次函數之間保持它的值。
如果沒有為一個預定義類型的靜態變量提供一個初始值的話,編譯器會確保在程序開始時為它初始化為0(將轉化為適當的類型)。
2.函數體內部的靜態對象
用戶自定義的靜態變量同一般的靜態對象的規則是一樣的,而且它同樣也必須有初始化操作。
但是,零賦值只對預定義類型有效,用戶自定義類型必須用構造函數來初始化。因此,如果我們
在定義一個靜態對象時沒有指定構造函數參數,這個類就必須有缺省的構造函數。
tips:
在C++中,全局靜態對象的構造函數是在main()之前調用的,所以我們現在有了一個在進
入main()之前執行一段代碼的簡單的、可移植的方法,並且可以在退出main()之後用析構函數
執行代碼。在C中要做到這一點,我們不得不熟悉編譯器開發商的匯編語言的開始代碼。
3.控制連接
一般情況下,在文件范圍內的所有名字(既不嵌套在類或函數中的名字)對程序中的所有
編譯單元來說都是可見的。這就是所謂的外部連接,因為在連接時這個名字對連接器來說是可
見的,外部的編譯單元、全局變量和普通函數都有外部連接。
有時我們可能想限制一個名字的可見性。想讓一個變量在文件范圍內是可見的,這樣這個
文件中的所有函數都可以使用它,但不想讓這個文件之外的函數看到或訪問該變量,或不想這
個變量的名字與外部的標識符相沖突。
在文件范圍內,一個被明確聲明為static的對象或函數的名字對編譯單元(一個.cpp文件)來說是局部變量;這些名字有內部連接。這意味著我們可以在其他的編譯單元中使用同樣的名字,而不會發生名字沖突。
內部連接的一個好處是這個名字可以放在一個頭文件中而不用擔心連接時發生沖突。那些
通常放在頭文件裡的名字,像常量、內聯函數( inline function),在缺省情況下都是內部連接
的(當然常量只有在C + +中缺省情況下是內部連接的,在C中它缺省為外部連接)。注意連接只
引用那些在連接/裝載期間有地址的成員,因此類聲明和局部變量並沒有連接。
• 沖突問題
下面例子說明了static的兩個含義怎樣彼此交叉的。所有的全局對象都是隱含為靜態存儲類,
所以如果我們定義(在文件范圍)
int a=0;
則a被存儲在程序的靜態數據區,在進入main()函數之前,a即已初始化了。另外,a對全局都是可
見的,包括所有的編譯單元。用可見性術語,s t a t i c(只在編譯單元內可見)的反義是e x t e r n,它
表示這個名字對所有的編譯單元都是可見的。所以上面的定義和下面的定義是相同的。
extern int a=0;
但如果這樣定義:
static int a=0;
我們只不過改變了a的可見性,現在a成了一個內部連接。但存儲類型沒有改變—對象總是駐
留在靜態數據區,而不管是s t a t i c還是e x t e r n。
一旦進入局部變量,s t a t i c就不會再改變變量的可見性(這時e x t e r n是沒有意義的),而只
是改變變量的存儲類型。
對函數名,s t a t i c和e x t e r n只會改變它的可見性,所以如果說:
extern void f();
它和沒有修飾時的聲明是一樣的:
void f();
如果定義:
static void f();
它意味著f ( )只在本編譯單元內是可見的,這有時稱作文件靜態。
4.其他的存儲類型指定符
我們會看到s t a t i c和e x t e r n用得很普遍。另外還有兩個存儲類型指定符,這兩種用得較少。
一個是a u t o ,人們幾乎不用它,因為它告訴編譯器這是一個局部變量,實際上編譯器總是可以從
變量定義時的上下文中判斷出這是一個局部變量。所以a u t o是多余的。還有一個是r e g i s t e r,它
也是局部變量,但它告訴編譯器這個特殊的變量要經常用到,所以編譯器應該盡可能地讓它保
存在寄存器中。它用於優化代碼。各種編譯器對這種類型的變量處理方式也不盡相同,它們有
時會忽略這種存儲類型的指定。一般,如果要用到這個變量的地址, r e g i s t e r指定符通常都會被
忽略。應該避免用r e g i s t e r類型,因為編譯器在優化代碼方面通常比我們做得更好。