C語言中文網 目錄

變量的作用域和存儲方式,C語言變量作用域和存儲方式完全攻略

變量按作用域可分為“局部變量”和“全局變量”。按存儲方式又可分為“自動變量(auto)”、“靜態變量(static)”、“寄存器變量(register)”和“外部變量(extern)”。注意,這里的“自動變量”不是指的“動態變量”。

什么叫“寄存器”?我們知道,內存條是用來存儲數據的,硬盤也是存儲數據的,而在 CPU 內部也有一些用來存儲數據的區域,即寄存器。寄存器是 CPU 的組成部分,是 CPU 內部用來存放數據的一些小型存儲區域,用來暫時存放參與運算的數據和運算結果。它同內存一樣,只不過它能存儲的數據要少得多。

局部變量

局部變量是定義在函數內部的變量,全局變量是定義在函數外部的變量。

局部變量只在本函數內有效,即只有在本函數內才能使用,在本函數外不能使用。如果局部變量定義在子函數中,那么只能在該子函數中使用。該子函數調用完后,系統為該子函數中的局部變量分配的內存空間就會被釋放掉。

如果局部變量定義在主函數 main 中,那么只能在 main 函數中使用,main 函數執行結束后,系統為其中的局部變量分配的內存空間就會被釋放掉。主函數也不能使用其他函數中定義的變量。所以不同函數中可以定義同名的變量,但它們所表示的是不同的對象,互不干擾。

在一個函數內部,可以在復合語句中定義變量,這些變量只在本復合語句中有效,離開本復合語句就無效,且內存單元隨即被釋放。所謂復合語句就是用大括號“{}”括起來的多個語句。下面給大家舉一個例子:
# include <stdio.h>
int main(void)
{
    int a = 1, b = 2;
    {
        int c;
        c = a + b;
    }
    printf("c = %d\n", c);
    return 0;
}
編譯結果:
error C2065: 'c' : undeclared identifier

這個程序有一個錯誤,編譯結果顯示:“變量 c 身份不明”,也就是說系統不認識 c 是什么。變量 a 和 b 在整個 main 函數中都有效,但變量 c 是在復合語句中定義的,所以只有在復合語句中才能使用。printf 在復合語句外,所以不能使用在復合語句中定義的變量 c。如果把 printf 放到復合語句中程序就編譯成功了。
# include <stdio.h>
int main(void)
{
    int a = 1, b = 2;
    {
        int c;
        c = a + b;
        printf("c = %d\n", c);
    }
    return 0;
}
輸出結果:
c = 3

我們在編程的時候,if 語句、for 循環、while 循環下面都有大括號。這就意味著如果局部變量是定義在這些大括號中的,那么就只能在這些大括號中使用。當這些大括號執行完之后,在其中定義的局部變量隨即就會被釋放。函數的形參也是局部變量,調用完該函數后,形參也會隨之被釋放。

所以局部變量的作用范圍準確地說不是以函數來限定的,而是以大括號“{}”來限定的。在一個大括號中定義的局部變量就只能在這個大括號中使用,但因為每個函數體都是用大括號括起來的,而且我們一般也不會在函數體中的其他大括號中定義變量,所以習慣上就說“局部變量是定義在‘函數’內部的變量,只在本‘函數’內有效”。

全局變量

定義在函數內部的變量叫作局部變量,定義在函數外部的變量叫作全局變量。局部變量只能在該函數內才能使用;而全局變量可以被整個 C 程序中所有的函數所共用。它的作用范圍是從定義的位置開始一直到整個 C 程序結束。所以根據定義位置的不同,全局變量的作用范圍不同。

在一個函數中既可以使用本函數中的局部變量,也可以使用有效的全局變量。但需要注意的是,全局變量和局部變量命名決不能沖突。

前面說過,不同函數中可以定義同名的變量,但它們代表的是不同的對象,所以互不干擾,原因是局部變量的作用范圍不同。但是全局變量和局部變量的作用范圍可能有重疊,這時局部變量和全局變量的名字就不能定義成相同的了,否則就會互相干擾。如果在同一個作用范圍內局部變量和全局變量重名,則局部變量起作用,全局變量不起作用。這樣局部變量就會屏蔽全局變量。下面寫一個程序驗證一下:
# include <stdio.h>
int a = 10;  //定義一個全局變量
int main(void)
{   
    int a = 5;  //定義一個同名的局部變量
    printf("a = %d\n", a);
    return 0;
}
輸出結果是:
a = 5

所以定義變量的時候局部變量和全局變量最好不要重名。此外,如果局部變量未初始化,那么系統會自動將“–858993460”放進去(VC++6如此)。但如果全局變量未初始化,那么系統會自動將其初始化為 0。它們的這個區別主要源自于它們存儲空間的不同。局部變量是在棧中分配的,而全局變量是在靜態存儲區中分配的。只要是在靜態存儲區中分配的,如果未初始化則系統都會自動將其初始化為 0。下面來寫一個程序驗證一下:
# include <stdio.h>
int a;  //定義一個全局變量
int main(void)
{   
    printf("a = %d\n", a);
    return 0;
}
輸出結果是:
a = 0

設置全局變量的作用是增加函數間數據聯系的渠道。因為全局變量可以被多個函數所共用,這樣就可以不通過實參和形參向被調函數傳遞數據。所以利用全局變量可以減少函數中實參和形參的個數,從而減少內存空間傳遞數據時的時間消耗。但是除非是迫不得已,否則不建議使用全局變量。

為什么不建議使用全局變量

1) 全局變量在程序的整個執行過程中都占用存儲單元,而局部變量僅在需要時才開辟存儲單元。

2) 全局變量降低了函數的通用性,因為函數在執行時要依賴那些全局變量。我們將一個功能編寫成函數,往往希望下次遇到同樣功能的時候直接復制過去就能使用。但是如果使用全局變量的話,這個函數和全局變量有聯系,就不好直接復制過去了,還要考慮這個全局變量的作用。即使把全局變量也復制過去,但是如果該全局變量和其他文件中的變量重名的話,程序就會出問題。而且如果該全局變量和其他函數也有關聯的話,程序也會出問題。所以全局變量降低了程序的可靠性和通用性。

在程序設計中,劃分模塊時要求模塊的“內聚性”強、與其他模塊的“關聯性”弱。即模塊的功能要單一,不要把許多互不相干的功能放到一個模塊中,與其他模塊間的相互影響要盡量小。而使用全局變量是不符合這個原則的。一般要求把C程序中的函數做成一個封閉體,除了可以通過“實參–形參”的渠道與外界發生聯系外,沒有其他渠道。這樣的程序可移植性好,可讀性強,而使用全局變量會使程序各函數之間的關系變得極其復雜。

3) 過多的全局變量會降低程序的清晰性。人們往往難以清楚地判斷出每個時刻各個全局變量的值,原因是每個函數在執行的時候都有可能改變全局變量的值。這樣程序就很容易出錯,而且邏輯上很亂。因此要限制全局變量的使用,不到萬不得已,不要使用全局變量。

4) 如果在同一個程序中,全局變量與局部變量同名,則在局部變量的作用范圍內,全局變量就會被“屏蔽”,即它不起作用。

自動變量(auto)

前面定義的局部變量其實都是 auto 型,只不過 auto 可以省略,因為省略就默認是 auto 型。

靜態變量(static)

static 還是比較重要的,static 可以用于修飾局部變量,也可以用于修飾全局變量。下面分別介紹一下。

用 static 修飾過的局部變量稱為靜態局部變量。局部變量如果不用 static 進行修飾,那么默認都是 auto 型的,即存儲在棧區。而定義成 static 之后就存儲在靜態存儲區了。

前面說過,存儲在靜態存儲區中的變量如果未初始化,系統會自動將其初始化為 0。用 static 修飾過的局部變量也不例外,下面寫一個程序驗證一下:
# include <stdio.h>
int main(void)
{   
    static int a;
    printf("a = %d\n", a);
    return 0;
}
輸出結果是:
a = 0

靜態存儲區主要用于存放靜態數據和全局數據。存儲在靜態存儲區中的數據存在于程序運行的整個過程中。所以靜態局部變量不同于普通局部變量,靜態局部變量是存在于程序運行的整個過程中的。下面來寫一個程序看一下:
# include <stdio.h>
void Increment(void);  //函數聲明
int main(void)
{
    Increment();
    Increment();
    Increment();
    return 0;
}
void Increment(void)
{
    static int x = 0;
    int y = 0;
    ++x;
    ++y;
    printf("x = %d, y = %d", x, y);
    printf("\n");
    return;
}
輸出結果是:
x = 1, y = 1
x = 2, y = 1
x = 3, y = 1

我們看到,變量 x 是靜態局部變量,而變量 y 是普通的局部變量。從輸出結果可以看出,y 的值一直都是 1,而 x 的值是變化的。這是因為變量 y 是普通的局部變量,每次函數調用結束后就會被釋放;而變量 x 是用 static 修飾的,所以它是靜態局部變量,即使函數調用結束它也不會被釋放,而是一直存在于程序運行的整個過程中,直到整個程序運行結束后才會被釋放。這就相當于全局變量,但它不是全局變量,靜態局部變量仍然是局部變量,仍然不能在它的作用范圍之外使用。

有人可能會問,如果靜態局部變量在函數調用結束后也不釋放,那函數每調用一次變量 x 不就重新定義一次,并重新賦值一次了嗎?

靜態局部變量僅在第一次函數調用時定義并初始化,以后再次調用時不再重新定義和初始化,而是保留上一次函數調用結束后的值。也就是說“static int x=0;”這條語句只會執行一次。

下面再來看全局變量。全局變量默認都是靜態的,都是存放在靜態存儲區中的,所以它們的生存周期固定,都是存在于程序運行的整個過程中。所以一個變量生命周期的長短本質上是看它存儲在什么地方。存儲在棧區中的變量,在函數調用結束后內存空間就會被釋放;而存儲在靜態存儲區中的變量會一直存在于程序的整個運行過程中。這就是局部變量和全局變量生命周期不同的原因。

雖然全局變量本身就是存儲在靜態存儲區的,但它仍然可以用 static 進行修飾。而且修飾和不修飾是有區別的。用 static 修飾全局變量時,會限定全局變量的作用范圍,使它的作用域僅限于本文件中。這個是使用 static 修飾全局變量的主要目的。

那么,不用 static 修飾,全局變量不也是只能在本文件中使用嗎?這么說不完全對,因為雖然全局變量的作用范圍不會自己主動作用到其他文件中,但不代表其他文件不會使用它。如果不用 static 進行修飾,那么其他文件只需要用 extern 對該全局變量進行一下聲明,就可以將該全局變量的作用范圍擴展到該文件中。但是當該全局變量在定義時用static進行修飾后,其他文件不論通過什么方式都不能訪問該全局變量。

而且如果一個項目的多個 .c 文件中存在同名的全局變量,那么在編譯的時候就會報錯,報錯的內容是“同一個變量被多次定義”。但是如果在這些全局變量前面都加上 static,那么編譯的時候就不會報錯。因為用 static 修飾后,這些全局變量就只屬于各自的 .c 文件了,它們是相互獨立的,所以編譯的時候就不會發生沖突而產生“多次定義”的錯誤。所以使用 static 定義全局變量是非常有用的,因為當一個項目中有很多文件的時候,重名不可避免。這時候只要在所有的全局變量前面都加上 static 就能很好地解決這個問題。定義全局變量時用 static 進行修飾可以大大提高代碼的質量。

總結:
1) 用 static 修飾后的局部變量稱為靜態局部變量。因為局部變量在函數調用結束后就會被釋放,所以如果不希望它被釋放的話,就在定義時用 static 對它進行修飾。如:
static int a;
2) 定義成 static 型的變量是存放在靜態存儲區中的,在程序的整個運行過程中都不會被釋放。這跟全局變量很像,但它不是全局變量。靜態局部變量仍然是局部變量,只不過它的生命周期改變了,但作用范圍并沒有改變,所以其他函數仍然不能使用它。它之所以跟全局變量很像是因為它們都是存放在靜態存儲區中,所以它們都存在于程序運行的整個過程中。

3) 如果把局部變量定義成 static 型,那么在程序的整個運行過程中,它只在編譯時賦初值一次,以后每次調用函數時不會再重新給它賦值,而是保留上一次函數調用結束時的值。如果定義靜態局部變量時沒有初始化的話,那么在編譯時系統會自動給它賦初值 0(對數值型變量)或空字符 '\0'(對字符變量)。

4) 但是靜態局部變量跟全局變量一樣,長期占用內存不放,而且降低了程序的可讀性,當調用次數過多時往往弄不清楚其當前值是什么。所以若非必要,不要過多使用靜態局部變量。

5) 對全局變量使用 static 進行修飾是 static 非常重要的用法。如果不用 static 對全局變量進行修飾,那么其他文件只需要使用 extern 對該全局變量進行聲明就可以直接使用該全局變量。但是當用 static 進行修飾后,即使其他文件用 extern 對它進行聲明也不能使用它。這種用法可以有效防止并解決不同文件中全局變量重名的問題。

寄存器變量(register)

無論是存儲在靜態存儲區中還是存儲在棧區中,變量都是存儲在內存中的。當程序用到哪個變量的時候,CPU 的控制器就會發出指令將該變量的值從內存讀到 CPU 里面。然后 CPU 再對它進行處理,處理完了再把結果寫回內存。

但是除了內存可以存儲數據之外,CPU 內部也有一些用來存儲數據的區域,這就是寄存器。寄存器是 CPU 的組成部分,是 CPU 內部用來存放數據的小型存儲區域,用來暫時存放參與運算的數據和運算結果。與內存相比,寄存器所能存儲的數據要小得多,但是它的存取速度要比內存快很多。

那為什么寄存器的存取速度比內存快呢?最主要的原因是因為它們的硬件設計不同。計算機中硬件運行速度由快到慢的順序是:

寄存器>緩存>內存>固態硬盤>機械硬盤

為了提高代碼執行的效率,可以考慮將經常使用的變量存儲到寄存器中。比如循環變量和循環體內每次循環都要使用的局部變量。這種變量叫作寄存器變量,用關鍵字 register 聲明。如
register  int  a;
但是需要注意的是,register 關鍵字只是請求編譯器盡可能地將變量存儲在 CPU 內部的寄存器中,但并不一定。因為我們說過,寄存器所能存儲的數據是很少的,所以如果寄存器已經滿了,那么即使你用 register 進行聲明,數據仍然會被存儲到內存中。而且需要注意的是,并不是所有的變量都能定義成寄存器變量,只有局部變量才可以。或者說寄存器變量也是一個局部變量,在函數調用結束后就會被釋放。

register 看起來很有用,但是要跟大家說的是,這個修飾符現在已經不用了。現在的編譯器都比較智能化,它會自動分析,即使變量定義的是 auto 型(默認的),但如果它發現這個變量經常使用,那么它也會把這個變量放到寄存器中。同樣,即使定義成 register 型,但如果寄存器中沒地方存放,那么它也會把這個變量放到內存中。

這時有些人就提出一個疑問:“既然寄存器速度那么快,那么為什么不把計算機的內存和硬盤都改成寄存器?我們前面說過,寄存器之所以比內存速度快,最主要的原因是因為它們的硬件設計不同。從硬件設計的角度來看,寄存器的制作成本要比內存高很多!而且寄存器數量的增加對 CPU 的性能也提出了極高的要求,而這往往是很難實現的。

外部變量(extern)

extern 變量是針對全局變量而言的。通過前面了解到,全局變量都是存放在靜態存儲區中的,所以它們的生存期是固定的,即存在于程序的整個運行過程中。但是對于全局變量來說,還有一個問題尚待解決,就是它的作用域究竟從什么位置起,到什么位置結束。作用域是包括整個文件范圍,還是只包括文件中的一部分?是在一個文件中有效,還是在程序的所有文件中都有效?

一般來說,外部變量是在函數的外部定義的全局變量,它的作用域是從變量的定義處開始,到本程序文件的末尾結束。在此作用域內,全局變量可以被程序中各個函數所引用。但是有時程序設計人員希望能擴展全局變量的作用域,如以下幾種情況:

1) 在一個文件內擴展全局變量的作用域

如果全局變量不在文件的開頭定義,其有效的作用范圍只限于定義處到文件結束。在定義點之前的函數不能引用該全局變量。如果出于某種考慮,在定義點之前的函數需要引用該全局變量,那么就在引用之前用關鍵字extern對該變量作“外部變量聲明”,表示將該全局變量的作用域擴展到此位置,比如:
extern  int  a;
有了此聲明就可以從“聲明”處起,合法地使用該全局變量了。

2) 將外部變量的作用域擴展到其他文件

一個 C 程序可以由一個或多個 .c 文件組成。如果程序只由一個 .c 文件組成,那么使用全局變量的方法前面已經介紹了。如果程序由多個 .c 文件組成,那么如何在一個文件中引用另一個文件中定義的全局變量呢?

假如在 1.c 中定義了全局變量“int a=10;”,如果 2.c 和 3.c 也想使用這個變量 a,而我們不能在 2.c 和 3.c 中重新定義這個 a,否則在編譯鏈接時就會出現“重復定義”的錯誤。正確的做法是在 2.c 和 3.c 中分別用 extern 對 a 作“外部變量聲明”,即在 2.c 和 3.c 中使用 a 之前作如下聲明:

這樣在編譯和鏈接時,當編譯器在 2.c 中遇到變量 a 的聲明時就會知道它有“外部鏈接”,就會從其他文件中尋找a的定義,并將在另一文件中定義的全局變量a的作用范圍擴展到本文件中。這樣在本文件中也可以合法地使用全局變量 a 了。但是現在問大家一個問題:“以 2.c 為例,如果在 2.c 中對 a 進行多次聲明,即寫多個“externinta;”,那么程序會不會有錯?”答案是不會,C 語言中是允許多次聲明的,但有效的只有一個。

與全局變量一樣,同一個項目的不同 .c 文件中也不能定義重名的函數。如果 2.c 和 3.c 想要使用 1.c 中定義的函數,那么也只需要在 2.c 和 3.c 中分別對該函數進行聲明就可以了。即直接把 1.c 中對函數的聲明拷貝過來就行了。

與全局變量不同的是,它的前面不用加 extern。因為對于函數而言,默認就是 extern,所以聲明時前面的 extern 可以省略。此外在實際編程中,我們一般不會直接把 1.c 中某個函數的聲明拷貝到 2.c 和 3.c 中,而是把該聲明寫在一個 .h 頭文件中,然后分別在 2.c 和 3.c 中用 #include 包含該頭文件即可。如果不用頭文件當然也可以,但此時 2.c 和 3.c 中都要寫該函數的聲明,這樣如果要修改的話就都要修改。而如果寫在頭文件中,要修改的話就只需要改頭文件中的內容就可以了。

關于頭文件后面還會專門介紹。此外,同一個 .c 文件中對同一個函數進行多次聲明也是允許的,但它們起作用的只有一個。

精美而實用的網站,提供C語言C++STLLinuxShellJavaGo語言等教程,以及socketGCCviSwing設計模式JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo