1. 程式人生 > >C/C++中儲存型別修飾符的區別(auto、static、register、extern、volatile、restrict)

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