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
的含義取決於上下文,