C語言存儲類別和鏈接
- 2019 年 11 月 4 日
- 筆記
目錄
C語言存儲類別和鏈接
最近詳細的複習C語言,看到存儲類別的時候總感覺一些概念模糊不清,現在認真的梳理一下。C語言的優勢之一能夠讓程式設計師恰到好處的控制程式,可以通過C語言的記憶體管理系統指定變數的作用域和生存周期,實現對程式的控制。
存儲類別
- 基本概念
對象:在C語言中所有的數據都會被存儲到記憶體中,被存儲的值會佔用一定的物理記憶體,這樣的一塊記憶體被稱為對象,它可以儲存一個或者多個值,在儲存適當的值時一定具有相應的大小。(C語言對象不同於面向對象語言的對象)
標識符:程式需要一種方法來訪問對象,這就需要聲明變數來實現,例如: int identifier = 1,在這裡identifier就是一個標識符,標識符是一個名稱並遵循變數的命名規則。所以在本例中identifier即是C程式指定硬體記憶體中的對象的方式並提供了存儲的值的大小「1」。在其它的情況中 int * pt、int arr[10],pt就是一個標誌符,它指定了儲存地址的變數,但是表達式*p不是一個標誌符,因為它不是一個名稱。arr的聲明創建了一個可容納10個int類型元素的對象,該數組的每一個元素也是一個對象。
作用域:描述程式中可訪問標識符的區域。因為一個C變數的作用域可以是塊作用域、函數作用域、文件作用域和函數原型作用域。
塊作用域:簡單來說塊作用域就是一對花括弧括起來的程式碼區域。定義在塊中的變數具有塊作用域,範圍是定義處到包含該定義塊的末尾。
函數原型作用域:範圍是從形參定義處到函數原型聲明的結束。我們知道編譯器在處理函數形參時只關心它的類型,而形參的名字通常無關緊要。例如:
void fun(int n,double m); 同樣可以聲明為 void fun(int ,double ); 還有一點要注意的是函數體的形參雖然聲明在函數的左花括弧之前但是它具有的是塊作用域屬於函數體這個塊。
文件作用域:變數的定義在所有函數的外面,從它的定義處到該文件的末尾處均可見稱這個變數擁有文件作用域。所以文件作用域變數也被稱為全局變數
鏈接:C變數有三種鏈接屬性:內部鏈接、外部鏈接和無鏈接。具有塊作用域、函數原型作用域的變數都是無鏈接變數,這就意味這他們屬於定義他們的塊或者函數原型私有。文件作用域變數可以是外部鏈接或是內部鏈接,外部鏈接可以在多個文件中使用,內部鏈接只能定義它的文件單元中使用。
存儲期
指對象在記憶體中保留了多長時間,作用域和鏈接描述了對象的可見性。存儲期則描述了標識符訪問對象的生存期。
C對象有4種存儲期:
1) 靜態存儲期:如果一個對象具有靜態存儲期,那麼它在程式的執行期間一直存在。文件作用域變數具有靜態存儲期,注意關鍵字static表明的是鏈接屬性而不是存儲期。以static聲明的文件作用域變數具有內部鏈接,無論具有內部鏈接還是外部鏈接,所有的文件作用域變數都具有靜態存儲期。
還有一種情況塊作用域變數也可以擁有靜態存儲期,把變數聲明在塊中並在變數名前加static關鍵字,例:
int fun(int num) { static int Index; ... } 在這裡變數Index就被存儲在靜態記憶體中,從程式被載入到程式結束都會存在,但是只有程式進入這個塊中才會訪問它指定的對象。
2) 執行緒存儲期:用於並發程式設計,一個程式的執行可以分為多個執行緒,具有執行緒存儲期的變數從被聲明時到執行緒結束一直存在。以關鍵字_Thread_local聲明一個對象時,每個執行緒都獲得該變數的私有備份。
3) 自動存儲期:塊作用域變數通常具有自動存儲期,當程式進入定義這些變數的塊時,會為這些變數分配記憶體,當程式離開這個塊時會自動釋放變數佔用的記憶體,這種做法相當於把自動變數佔用的記憶體視為可重複利用的工作區或暫存區。
4) 動態分配存儲期
五種存儲類別
- 五種存儲類別
| 存儲類別 | 存儲期 | 作用域 | 鏈接 | 聲明方式 |
|---|---|---|---|---|
| 自動 | 自動 | 塊 | 無鏈接 | 塊內 |
| 暫存器 | 自動 | 塊 | 無鏈接 | 塊內 關鍵字regsiter |
| 靜態外部鏈接 | 靜態 | 文件 | 外部 | 所有函數外 |
| 靜態內部鏈接 | 靜態 | 文件 | 外部 | 所有函數外 關鍵字static |
| 靜態無鏈接 | 靜態 | 塊 | 無 | 塊內 關鍵字static |
- 自動變數
自動變數屬於自動識別的變數具有自動存儲期,塊作用域且無鏈接。可以顯示的使用auto關鍵字進行聲明。
注意: auto是存儲類別說明符和C++中的auto用法完全不同
一個變數具有自動存儲期就意味著當程式進入這個塊時變數存在,退出塊時變數消失,原來變數佔用的記憶體另作他用。
void hiding() { int x = 30; printf("x in outer block: %d at %pn", x, &x); { x = 77; printf("x in inner block: %d at %pn", x, &x); } // 塊中記憶體被釋放隱藏的x恢復 x = 30 printf("x in outer block: %d at %pn", x, &x); while (x++ < 33) { int x = 100; x++; printf("x in while loop: %d at %pn", x, &x); } printf("x in outer block: %d at %pn", x, &x); } 沒有花括弧時
void forc() { int n = 8; printf(" Initially, n = %d at %pn", n, &n); for (int n = 1; n < 3; ++n) printf(" loop 1:n = %d at %pn", n &n); // 離開循環後原始的你又起作用了 printf("After loop 1:n = %d at %pn", n &n); for (int n = 1; n < 3; ++n) { printf("loop 2 index n = %d at %pn", n, &n); // 重新初始化的自動變數,作用域沒有到循環里的n int n = 6; printf(" loop 2:n = %d at %pn", n, &n); // 起作用的仍然是循環中的n n++; } // 離開循環後原始的n又起作用了 printf(" loop 2:n = %d at %pn", n, &n); } 輸出為

- 暫存器變數
使用關鍵字register,儲存在CPU的暫存器中,存儲在最快的可用記憶體中。
- 塊作用域的靜態變數
首先要明確概念靜態變數並不是指值不改變的變數,而是指它在記憶體中的位置不變。具有文件作用域的靜態變數自動具有靜態存儲期。
前面提到我們可以創建一個靜態存儲期,塊作用域的局部變數,這種變數和自動變數一樣具有相同的作用域,但是在程式離開塊時並不會消失,
void trystat(); int main() { int count = 1; for (count = 1; count <= 3; count++) { printf("Here comes iteration %d:n", count); trystat(); } trystat(); return 0; } void trystat() { int fade = 1; static int stay = 1; printf(" fade = %d and stay = %dn", fade++, stay++); } 輸出:
可以看出每次離開塊fade變數的值都會被重新的初始化,而stay只是在編譯函數void trystat()的時候被初始化了一次,在離開自己函數體的塊和for循環塊之後都會遞增,說明stay訪問的對象一直存在並沒有像自動變數一樣被釋放掉。
- 外部鏈接的靜態變數
具有外部鏈接、靜態存儲期和文件作用域,屬於該類別的變數屬於外部變數。只需要把變數的聲明放在所有函數的外面就創建了一個外部變數。為了表明該函數使用了外部變數,需要使用關鍵字extern來再次申明。如果在一個源文件中使用的外部變數聲明在了另一個源文件中,則必須要使用extern來申明。
外部變數可以顯示初始化,如果沒有則會被默認初始化為0。
- 內部鏈接的靜態變數
具有文件作用域、靜態存儲期和內部鏈接,在所有函數外用static來聲明一個變數就是內部鏈接的靜態變數。
例:
static int val = 1; int main() { ... } 普通的外部變數可以用於程式中的任意一個函數處,但是內部鏈接的靜態變數只能用於同一個文件中的函數。都可以使用extern說明符,在函數中重複任何聲明文件作用域變數並不會改變他們的鏈接屬性。
例:
int global = 1; static int local_global = 2; int main { extern int global = 1; extern int local_global = 2; ... } 只有在多文件中才能區別內部鏈接和外部鏈接的重要性。
總結一下存儲類別的說明符中關鍵字共有六個auto、register、_Thread_local、static、extern和typedef ,其中static和extern的含義取決於上下文,