C語言儲存類別和連結
目錄
- C語言儲存類別和連結
- 儲存類別
- 儲存期
- 五種儲存類別
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 %p\n", x, &x);
{
x = 77;
printf("x in inner block: %d at %p\n", x, &x);
}
// 塊中記憶體被釋放隱藏的x恢復 x = 30
printf("x in outer block: %d at %p\n", x, &x);
while (x++ < 33)
{
int x = 100;
x++;
printf("x in while loop: %d at %p\n", x, &x);
}
printf("x in outer block: %d at %p\n", x, &x);
}
沒有花括號時
void forc()
{
int n = 8;
printf(" Initially, n = %d at %p\n", n, &n);
for (int n = 1; n < 3; ++n)
printf(" loop 1:n = %d at %p\n", n &n);
// 離開迴圈後原始的你又起作用了
printf("After loop 1:n = %d at %p\n", n &n);
for (int n = 1; n < 3; ++n)
{
printf("loop 2 index n = %d at %p\n", n, &n);
// 重新初始化的自動變數,作用域沒有到迴圈裡的n
int n = 6;
printf(" loop 2:n = %d at %p\n", n, &n);
// 起作用的仍然是迴圈中的n
n++;
}
// 離開迴圈後原始的n又起作用了
printf(" loop 2:n = %d at %p\n", 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 = %d\n", 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
的含義取決於上下