C/C++中儲存型別修飾符的區別(auto、static、register、extern、volatile、restrict)
一、區域性變數和全域性變數:
(1)區域性變數:區域性變數也叫自動變數,它宣告在函式開始,生存於棧,它的生命隨著函式的返回而結束。
#include <stdio.h>
int main(void)
{
auto int i = 9; //宣告區域性變數的關鍵字是 auto; 因可以省略,所以幾乎沒人使用
printf("%d\n", i);
getchar();
return 0;
}
(2)全域性變數:全域性變數宣告在函式體外,一般應在函式前。每個函式都可以使用它,不過全域性變數應儘量少用。
#include <stdio.h> void add(void); void mul(void); int gi = 3; //全域性變數(宣告在函式外) int main(void) { printf("%d\n", gi); //輸出的是 3 add(); printf("%d\n", gi); //輸出的是 5 mul(); printf("%d\n", gi); //輸出的是 10 getchar(); return 0; } void add(void) { gi += 2; } void mul(void) { gi *= 2; }
#include <stdio.h>
int gi;//全域性變數
int main(void)
{
int i;//區域性變數
printf("%d, %d\n", gi, i);
getchar();
return 0;
}
當全域性變數與區域性變數重名時,使用的是區域性變數:
#include <stdio.h> int a = 111, b = 222; int main(void) { int a = 123; printf("%d,%d\n", a, b);//輸出的是 123,222 getchar(); return 0; }
二、物件的生存週期(lifetime)
(1)靜態生存週期(即全域性變數的生存週期)
具有靜態生存週期的所有物件,都是在程式開始執行之前就被事先建立和初始化。它們的壽命覆蓋整個程式的執行過程。如在函式內定義了一個static變數,那第一次呼叫該函式後,該變數的值將會被保留,當第二次被呼叫時,該變數的值還是第一次呼叫結束時的值。
(2)自動生存週期(即區域性變數的生存週期)
自動生存週期的物件的壽命由“物件定義所處在的大括號{}”決定。每次程式執行流進入一個語句塊,此語句塊自動生存週期的物件就會被建立一個新例項,同時被初始化。
三、識別符號的連結(linkage)
(1)外部連結
表示在整個程式中(多個程式檔案)是相同的函式或物件。常見的有,在函式體外宣告的extern變數。
(2)內部連結
表示只在當前程式檔案中是相同的函式或物件。其它程式檔案不能對其進行訪問。常見的有,在函式體外宣告的static變數。
(3)無連結
一般宣告在函式內部的auto、register變數、還有函式的引數,都是無連結。它的作用域是函式內部。
四、儲存型別修飾符總結:
儲存型別修飾符可以修改識別符號的連結和對應物件的生存週期;識別符號有連結,而非生命週期;物件有生存週期,而非連結;函式識別符號只可用static、extern修飾,函式引數只可用register修飾。
(1)auto(對應自動生存週期)
auto修飾符只能用在函式內的物件宣告中,即僅在語句塊內使用。
宣告中有auto修飾符的物件具有自動生存週期。
它們僅存在於被定義的當前執行程式碼塊中,即區域性變數在進入模組時生成,在退出模組時消亡。
定義區域性變數的最常見的程式碼塊是函式。 語言中包括了關鍵字auto,它可用於定義區域性變數。但自從所有的非全域性變數的預設值假定為auto以來,auto就幾乎很少使用了。
(2)static(對應靜態生存週期)
如果是定義在函式外,那麼該物件具有內部連結,其它程式檔案不能對其訪問。如果是定義在函式內,那麼該物件具有無連結,函式外不能對其訪問。
(注意:static變數初始化時,只能用常量)
用 static 關鍵字修飾的區域性變數稱為靜態區域性變數。
靜態區域性變數存值如同全域性變數,區別在於它只屬於擁有它的函式,它也和全域性變數一樣會被初始化為空。
#include <stdio.h>
void fun1(void);
void fun2(void);
int main(void)
{
int i;
for (i = 0; i < 10; i++) fun1();
printf("---\n");
for (i = 0; i < 10; i++) fun2();
getchar();
return 0;
}
void fun1(void) {
int n = 0;//一般的區域性變數
printf("%d\n", n++);
}
void fun2(void) {
static int n;//靜態區域性變數,會被初始化為空
printf("%d\n", n++);
}
用 static 關鍵字修飾的全域性變數是靜態全域性變數,靜態全域性變數只能用於定義它的單元。
//譬如在 File1.c 裡面定義了:
static int num = 99; /* 去掉前面的 static 其他單元才可以使用 */
//在 File2.c 裡使用:
#include <stdio.h>
extern int num;
int main(void)
{
printf("%d\n", num);
getchar();
return 0;
}
用靜態變數記錄函式被呼叫的次數:
#include <stdio.h>
int fun(void);
int main(void)
{
int i;
for (i = 0; i < 10; i++) {
printf("函式被呼叫了 %2d 次;\n", fun());
}
getchar();
return 0;
}
int fun(void) {
static int n;
return ++n;
}
(3)const
(4)extern(對應靜態生存週期)
extern 意為“外來的”。它的作用在於告訴編譯器:這個變數或者函式的定義在別的地方,當遇到此變數或函式時應到其他模組中尋找其定義。
(PS:這個變數,它可能不存在於當前的檔案中,但它肯定要存在於工程中的某一個原始檔中或者一個Dll的輸出中。)
#include <stdio.h>
extern int g1;
int main(void)
{
extern int g2;//告訴編譯器g2定義在其他地方
printf("%d,%d\n", g1,g2);
getchar();
return 0;
}
int g1 = 77;
int g2 = 88;
使用extern時,注意不能重複定義,否則編譯報錯,如:
程式檔案一:
extern int a = 10; //編譯警告,extern的變數最好不要初始化
程式檔案二:
extern int a = 20; //重複定義,應改為extern int a;
(一般最好這樣,如果需要初始化,可把extern修飾符去掉(但也不要重複定義),另外如果其它程式檔案也需要用到該變數,可用extern來宣告該變數。這樣會比較清晰。)
另外,extern也可用來進行連結指定。
(5)volatile
(6)register(即暫存器變數,對應自動生存週期)
當宣告物件有自動生存週期時,可以使用register修飾符。因此,register也只能用在函式內的宣告中。
register修飾符暗示編譯程式相應的變數將被頻繁地使用,如果可能的話,應將其儲存在CPU的暫存器中(而不是棧或堆),以加快其儲存速度。然而,編譯器不見得會這麼做,因此效果一般般。瞭解一下就行,不建議使用。
#include <stdio.h>
#include <time.h>
#define TIME 1000000000
int m, n = TIME;//全域性變數
int main(void)
{
time_t start, stop;
register int a, b = TIME;//暫存器變數
int x, y = TIME;//一般變數
time(&start);
for (a = 0; a < b; a++);
time(&stop);
printf("暫存器變數用時: %d 秒\n", stop - start);
time(&start);
for (x = 0; x < y; x++);
time(&stop);
printf("一般變數用時: %d 秒\n", stop - start);
time(&start);
for (m = 0; m < n; m++);
time(&stop);
printf("全域性變數用時: %d 秒\n", stop - start);
getchar();
return 0;
}
使用register修飾符有幾點限制:
首先,register變數必須是能被CPU所接受的型別。這通常意味著register變數必須是一個單個的值,並且長度應該小於或者等於整型的長度。不過,有些機器的暫存器也能存放浮點數。 其次,因為register變數有可能被存放到暫存器中而不是記憶體中,所以不能用“&”來獲取register變數的地址。 總的來說,由於暫存器的數量有限,而且某些暫存器只能接受特定型別的資料(如指標和浮點數),因此真正起作用的register修飾符的數目和型別都依賴於執行程式的機器,而任何多餘的register修飾符都將被編譯程式所忽略。 在某些情況下,把變數儲存在暫存器中反而會降低程式的執行速度。因為被佔用的暫存器不能再用於其它目的;或者變數被使用的次數不夠多,不足以裝入和儲存變數所帶來的額外開銷。 早期的C編譯程式不會把變數儲存在暫存器中,除非你命令它這樣做,這時register修飾符是C語言的一種很有價值的補充。然而,隨著編譯程式設計技術的進步,在決定那些變數應該被存到暫存器中時,現在的C編譯環境能比程式設計師做出更好的決定。實際上,許多編譯程式都會忽略register修飾符,因為儘管它完全合法,但它僅僅是暗示而不是命令。(7)預設修飾符
函式內,與auto相同,函式外,與extern相同。
五、概括性例子:
int func1(void); //func1具有外部連結
int a = 10; //a具有外部連結,靜態生存週期
extern int b = 1; //b具有外部連結,靜態生存週期。但編譯會有警告extern變數不應初始化,同時也要注意是否會重複定義
static int c; //c具有內部連結,靜態生存週期
static int e; //e具有內部連結,靜態生存週期
static void func2(int d)
{
//func2具有內部連結;引數d具有無連結,自動生存週期
extern int a; //a與上面的a一樣(同一變數),具有外部連結,靜態生存週期。注意這裡的不會被預設初始為0,它只是個宣告
int b = 2; //b具有無連結,自動生存同期。並且將上面宣告的b隱藏起來
extern int c; //c與上面的c一樣,維持內部連結,靜態生存週期。注意這裡的不會被預設初始為0,它只是個宣告
//如果去掉了extern修飾符,就跟b類似了,無連結,自動生存週期,把上面宣告的c隱藏起來
static int e; //e具有無連結,靜態生存週期。並且將上面宣告的e隱藏起來;初始化值為0
static int f; //f具有無連結,靜態生存週期
}
相關連結參考:
http://developer.51cto.com/art/201105/261465.htm
http://apps.hi.baidu.com/share/detail/30353645
http://www.cnblogs.com/del/archive/2008/12/04/1347305.html