C++變量的存儲類別(動態存儲、靜態存儲、自動變量、寄存器變量、外部變量)
動態存儲方式與靜態存儲方式
我們已經了解了變量的作用域。作用域是從空間的角度來分析的,分為全局變量和局部變量。
變量還有另一種屬性——存儲期(storage duration,也稱生命期)。存儲期是指變量在內存中的存在期間。這是從變量值存在的時間角度來分析的。
存儲期可以分為靜態存儲期(static storage duration)和動態存儲期(dynamic storage duration)。這是由變量的靜態存儲方式和動態存儲方式決定的。
所謂靜態存儲方式是指在程序運行期間,系統對變量分配固定的存儲空間。而動態存儲方式則是在程序運行期間,系統對變量動態地分配存儲空間。
先看一下內存中的供用戶使用的存儲空間的情況。這個存儲空間可以分為三部分,即:
- 程序區
- 靜態存儲區
- 動態存儲區
數據分別存放在靜態存儲區和動態存儲區中。全局變量全部存放在靜態存儲區中,在程序開始執行時給全局變量分配存儲單元,程序執行完畢就釋放這些空間。在程序執行過程中它們占據固定的存儲單元,而不是動態地進行分配和釋放。
在動態存儲區中存放以下數據:
函數形式參數。在調用函數時給形參分配存儲空間。
函數中的自動變量(未加static聲明的局部變量,詳見后面的介紹)。
函數調用時的現場保護和返回地址等。
對以上這些數據,在函數調用開始時分配動態存儲空間,函數結束時釋放這些空間。在程序執行過程中,這種分配和釋放是動態的,如果在一個程序中兩次調用同一函數,則要進行兩次分配和釋放,而兩次分配給此函數中局部變量的存儲空間地址可能是不相同的。
如果在一個程序中包含若干個函數,每個函數中的局部變量的存儲期並不等於整個程序的執行周期,它只是整個程序執行周期的一部分。根據函數調用的情況,系統對局部變量動態地分配和釋放存儲空間。
在C++中變量除了有數據類型的屬性之外,還有存儲類別(storage class) 的屬性。存儲類別指的是數據在內存中存儲的方法。存儲方法分為靜態存儲和動態存儲兩大類。具體包含4種:自動的(auto)、靜態的(static)、寄存器的(register)和外部的(extern)。根據變量的存儲類別,可以知道變量的作用域和存儲期。
自動變量
函數中的局部變量,如果不用關鍵字static加以聲明,編譯系統對它們是動態地分配存儲空間的。函數的形參和在函數中定義的變量(包括在復合語句中定義的變量)都屬此類。在調用該函數時,系統給形參和函數中定義的變量分配存儲空間,數據存儲在動態存儲區中。在函數調用結束時就自動釋放這些空間。如果是在復合語句中定義的變量,則在變量定義時分配存儲空間,在復合語句結束時自動釋放空間。因此這類局部變量稱為自動變量(auto variable)。自動變量用關鍵字auto作存儲類別的聲明。例如:
- int f(int a) //定義f函數,a為形參
- {
- auto int b, c=3; //定義b和c為整型的自動變量
- }
存儲類別auto和數據類型int的順序任意。關鍵字auto可以省略,如果不寫auto,則系統把它默認為自動存儲類別,它屬於動態存儲方式。程序中大多數變量屬於自動變量。本教程前面各章所介紹的例子中,在函數中定義的變量都沒有聲明為auto,其實都默認指定為自動變量。在函數體中以下兩種寫法作用相同
- auto int b, c=3;
- int b, c=3;
用static聲明靜態局部變量
有時希望函數中的局部變量的值在函數調用結束后不消失而保留原值,即其占用的存儲單元不釋放,在下一次該函數調用時,該變量保留上一次函數調用結束時的值。這時就應該指定該局部變量為靜態局部變量(static local variable)。
【例】靜態局部變量的值。
- #include <iostream>
- using namespace std;
- int f(int a) //定義f函數,a為形參
- {
- auto int b=0; //定義b為自動變量
- static int c=3; //定義c為靜態局部變量
- b=b+1;
- c=c+1;
- return a+b+c;
- }
- int main( )
- {
- int a=2,i;
- for(i=0;i<3;i++)
- cout<<f(a)<<" ";
- cout<<endl;
- return 0;
- }
運行結果為:
7 8 9
先后3次調用f函數時,b和c的值如表所示。
對靜態局部變量的說明:
靜態局部變量在靜態存儲區內分配存儲單元。在程序整個運行期間都不釋放。而自動變量(即動態局部變量)屬於動態存儲類別,存儲在動態存儲區空間(而不是靜態存儲區空間),函數調用結束后即釋放。
為靜態局部變量賦初值是在編譯時進行值的,即只賦初值一次,在程序運行時它已有初值。以后每次調用函數時不再重新賦初值而只是保留上次函數調用結束時的值。而為自動變量賦初值,不是在編譯時進行的,而是在函數調用時進行,每調用一次函數重新給一次初值,相當於執行一次賦值語句。
如果在定義局部變量時不賦初值的話,對靜態局部變量來說,編譯時自動賦初值0(對數值型變量)或空字符(對字符型變量)。而對自動變量來說,如果不賦初值,則它的值是一個不確定的值。這是由於每次函數調用結束后存儲單元已釋放,下次調用時又重新另分配存儲單元,而所分配的單元中的值是不確定的。
雖然靜態局部變量在函數調用結束后仍然存在,但其他函數是不能引用它的,也就是說,在其他函數中它是“不可見”的。
在什么情況下需要用局部靜態變量呢?
1) 需要保留函數上一次調用結束時的值。例如可以用下例中的方法求n!。
【例】輸出1~5的階乘值(即1!,2!,3!,4!,5!)。
- #include <iostream>
- using namespace std;
- int fac(int); //函數聲明
- int main( )
- {
- int i;
- for(i=1;i<=5;i++)
- cout<<i<<"!="<<fac(i)<<endl;
- return 0;
- }
- int fac(int n)
- {
- static int f=1; //f為靜態局部變量,函數結束時f的值不釋放
- f=f*n; //在f原值基礎上乘以n
- return f;
- }
運行結果為
- 1!=1
- 2!=2
- 3!=6
- 4!=24
- 5!=120
每次調用fac(i),就輸出一個i,同時保留這個i!的值,以便下次再乘(i+1)。
2) 如果初始化后,變量只被引用而不改變其值,則這時用靜態局部變量比較方便,以免每次調用時重新賦值。 但是應該看到,用靜態存儲要多占內存,而且降低了程序的可讀性,當調用次數多時往往弄不清靜態局部變量的當前值是什么。
因此,如不必要,不要多用靜態局部變量。
用register聲明寄存器變量
一般情況下,變量的值是存放在內存中的。當程序中用到哪一個變量的值時,由控制器發出指令將內存中該變量的值送到CPU中的運算器。經過運算器進行運算,如果需要存數,再從運算器將數據送到內存存放。如圖所示。
為提高執行效率,C++允許將局部變量的值放在CPU中的寄存器中,需要用時直接從寄存器取出參加運算,不必再到內存中去存取。這種變量叫做寄存器變量,用關鍵字register作聲明。例如,可以將例4.14中的fac函數改寫如下:
- int fac(int n)
- {
- register int i,f=1; //定義i和f是寄存器變量
- for(i=1;i<=n;i++) f=f*i;
- return f;
- }
定義f和i是存放在寄存器的局部變量,如果n的值大,則能節約許多執行時間。
在程序中定義寄存器變量對編譯系統只是建議性(而不是強制性)的。當今的優化編譯系統能夠識別使用頻繁的變量,自動地將這些變量放在寄存器中。
用extern聲明外部變量
全局變量(外部變量)是在函數的外部定義的,它的作用域為從變量的定義處開始,到本程序文件的末尾。在此作用域內,全局變量可以為本文件中各個函數所引用。編譯時將全局變量分配在靜態存儲區。
有時需要用extern來聲明全局變量,以擴展全局變量的作用域。
1) 在一個文件內聲明全局變量
如果外部變量不在文件的開頭定義,其有效的作用范圍只限於定義處到文件終了。如果在定義點之前的函數想引用該全局變量,則應該在引用之前用關鍵字extern對該變量作外部變量聲明,表示該變量是一個將在下面定義的全局變量。有了此聲明,就可以從聲明處起,合法地引用該全局變量,這種聲明稱為提前引用聲明。
【例】用extern對外部變量作提前引用聲明,以擴展程序文件中的作用域。
- #include <iostream>
- using namespace std;
- int max(int,int); //函數聲明
- void main( )
- {
- extern int a,b;//對全局變量a,b作提前引用聲明
- cout<<max(a,b)<<endl;
- }
- int a=15,b=-7;//定義全局變量a,b
- int max(int x,int y)
- {
- int z;
- z=x>y?x:y;
- return z;
- }
運行結果如下:
在main后面定義了全局變量a,b,但由於全局變量定義的位置在函數main之后,因此如果沒有程序的第5行,在main函數中是不能引用全局變量a和b的。現在我們在main函數第2行用extern對a和b作了提前引用聲明,表示a和b是將在后面定義的變量。這樣在main函數中就可以合法地使用全局變量a和b了。如果不作extern聲明,編譯時會出錯,系統認為a和b未經定義。一般都把全局變量的定義放在引用它的所有函數之前,這樣可以避免在函數中多加一個extern聲明。
2) 在多文件的程序中聲明外部變量
如果一個程序包含兩個文件,在兩個文件中都要用到同一個外部變量num,不能分別在兩個文件中各自定義一個外部變量num。正確的做法是:在任一個文件中定義外部變量num,而在另一文件中用extern對num作外部變量聲明。即
- extern int num;
編譯系統由此知道num是一個已在別處定義的外部變量,它先在本文件中找有無外部變量num,如果有,則將其作用域擴展到本行開始(如上節所述),如果本文件中無此外部變量,則在程序連接時從其他文件中找有無外部變量num,如果有,則把在另一文件中定義的外部變量num的作用域擴展到本文件,在本文件中可以合法地引用該外部變量num。
分析下例:
filel.cpp
- extern int a,b;
- int main()
- {
- cout<<a<<","<<b<<end!;
- return 0;
- }
- file2.cpp
- int as3,b=4;
在源程序文件ffle2.cpp中定義了整型變量a和b,並賦了初值。在filel.cpp中用extern聲明外部變量a和b,未賦值。在編譯連接成一個程序后,file2.cpp中的a和b的作用域擴展到file2.cpp文件中,因此main函數中的cout語句輸出a和b的值為3和4。
用extern擴展全局變量的作用域,雖然能為程序設計帶來方便,但應十分慎重,因為在執行一個文件中的函數時,可能會改變了該全局變量的值,從而會影響到另一文件中的函數執行結果。
用static聲明靜態外部變量
有時在程序設計中希望某些外部變量只限於被本文件引用,而不能被其他文件引用。這時可以在定義外部變量時加一個static聲明。例如:
file1.cpp
- static int a=3;
- int main ( )
- {
- ┆
- }
file2.cpp
- extern int a;
- int fun (int n)
- {
- ┆
- a=a*n;
- ┆
- }
在filel.cpp中定義了一個全局變量a,但它用static聲明,因此只能用於本文件,雖然 在cpp文件中用了“extern int a;”,但file2.cpp文件中仍然無法使用filel.cpp中的全局變量a。
這種加上static聲明、只能用於本文件的外部變量(全局變量)稱為靜態外部變量。這就為程序的模塊化、通用性提供了方便。如果已知道其他文件不需要引用本文件的全局變量,可以對本文件中的全局變量都加上static,成為靜態外部變量,以免被其他文件誤用。
需要指出,不要誤認為用static聲明的外部變量才采用靜態存儲方式(存放在靜態存儲區中),而不加static的是動態存儲(存放在動態存儲區)。實際上,兩種形式的外部變量都用靜態存儲方式,只是作用范圍不同而已,都是在編譯時分配內存的。
C++變量屬性小結
一個變量除了數據類型以外,還有3種屬性:
存儲類別 C++允許使用auto,static,register和extern 4種存儲類別。
作用域 指程序中可以引用該變量的區域。
存儲期 指變量在內存的存儲期限。
以上3種屬性是有聯系的,程序設計者只能聲明變量的存儲類別,通過存儲類別可以確定變量的作用域和存儲期。
要注意存儲類別的用法。auto, static和register 3種存儲類別只能用於變量的定義語句中,如:
- auto char c; //字符型自動變量,在函數內定義
- static int a; //靜態局部整型變量或靜態外部整型變量
- register int d; //整型寄存器變量,在函數內定義
- extern int b; //聲明一個已定義的外部整型變量
說明: extern只能用來聲明已定義的外部變量,而不能用於變量的定義。只要看到extern,就可以判定這是變量聲明,而不是定義變量的語句。
下面從不同角度分析它們之間的聯系。
1) 從作用域角度分,有局部變量和全局變量。它們采用的存儲類別如下:
局部變量
自動變量,即動態局部變量(離開函數,值就消失)
靜態局部變量(離開函數,值仍保留)
寄存器變量(離開函數,值就消失)
形式參數(可以定義為自動變量或寄存器變量)
全局變量
靜態外部變量(只限本文件引用)
外部變量(即非靜態的外部變量,允許其他文件引用)
2) 從變量存儲期(存在的時間)來區分,有動態存儲和靜態存儲兩種類型。靜態存儲是程序整個運行時間都存在,而動態存儲則是在調用函數時臨時分配單元。
動態存儲
自動變量(本函數內有效)
寄存器變量(本函數內有效)
形式參數
靜態存儲
靜態局部變量(函數內有效)
靜態外部變量(本文件內有效)
外部變量(其他文件可引用)
3) 從變量值存放的位置。可分為:
內存中靜態存儲區
靜態局部變量
靜態外部變量(函數外部靜態變量)
外部變量(可為其他文件引用)
內存中動態存儲區: 自動變量和形式參數
CPU 中的寄存器: 寄存器變量
4) 關於作用域和存儲期的概念。
從前面敘述可以知道,對一個變量的性質可以從兩個方面分析,一是從變量的作用域,一是從變量值存在時間的長短,即存儲期。前者是從空間的角度,后者是從時間的角度。二者有聯系但不是同一回事。下圖是作用域的示意圖,下圖是存儲期的示意圖。
如果一個變量在某個文件或函數范圍內是有效的,則稱該文件或函數為該變量的作用域,在此作用域內可以引用該變量,所以又稱變量在此作用域內“可見”,這種性質又稱為變量的可見性,例如圖中變量a?b在函數f1中可見。
如果一個變量值在某一時刻是存在的,則認為這一時刻屬於該變量的存儲期,或稱該變量在此時刻“存在”。下表表示各種類型變量的作用域和存在性的情況。
其中“√”表示是,“X”表示否。可以看到自動變量和寄存器變量在函數內的可見性和存在性是一致的。在函數外的可見性和存在性也是一致的。靜態局部變量在函數外的可見性和存在性不一致。靜態外部變量和外部變量的可見性和存在性是一致的。
如果一個變量在某個文件或函數范圍內是有效的,則稱該文件或函數為該變量的作用域,在此作用域內可以引用該變量,所以又稱變量在此作用域內“可見”,這種性質又稱為變量的可見性,例如圖中變量a?b在函數f1中可見。
如果一個變量值在某一時刻是存在的,則認為這一時刻屬於該變量的存儲期,或稱該變量在此時刻“存在”。書中表表示各種類型變量的作用域和存在性的情況。
可以看到自動變量和寄存器變量在函數內的可見性和存在性是一致的。在函數外的可見性和存在性也是一致的。靜態局部變量在函數外的可見性和存在性不一致。靜態外部變量和外部變量的可見性和存在性是一致的。
5) static聲明使變量采用靜態存儲方式,但它對局部變量和全局變量所起的作用不同。
對局部變量來說,static使變量由動態存儲方式改變為靜態存儲方式。而對全局變量來說,它使變量局部化(局部於本文件),但仍為靜態存儲方式。從作用域角度看,凡有static聲明的,其作用域都是局限的,或者局限於本函數內(靜態局部變量),或者局限於本文件內(靜態外部變量)。