變數的儲存方式和生存期
動態儲存方式與靜態儲存方式
從變數的作用域(即從空間)的角度來觀察,變數可以分為全域性變數和區域性變數
從變數存在的時間(即生存期)來觀察:有的變數在程式執行的整個過程都是存在的,而有的變數則是在呼叫其所在的函式時才臨時分配儲存單元,而在函式呼叫結束後該儲存單元就馬上釋放了,變數不存在了。
變數的儲存有兩種不同的方式:靜態儲存方式和動態儲存方式
靜態儲存方式:是指在程式執行期間由系統分配固定的儲存空間的方式
動態儲存方式:是指在程式執行期間根據需要進行動態的分配儲存空間的方式
使用者區的儲存空間可以分為3個部分:
- 程式區
- 靜態儲存區
- 動態儲存區
資料分別存放在靜態儲存區和動態儲存區。全域性變數全部存放在靜態儲存區,在程式開始執行時給全域性變數分配儲存區,程式執行完畢就釋放。在程式執行過程中它們佔固定的儲存單元,而不是動態的進行分配和釋放
在動態儲存區存放以下資料:
- 函式形參。在呼叫函式時給形參分配儲存空間
- 函式中定義的沒有用關鍵字
static
宣告的變數,即自動變數 - 函式呼叫時的現場保護和返回地址等
對於動態儲存區的資料,在函式呼叫時分配動態儲存空間,函式結束時釋放這些空間
在程式執行過程中,這總分配和釋放是動態的,如果在一個程式中兩次呼叫同一函式,而在此函式中定義了區域性變數,在兩次呼叫時分配給這些區域性變數的儲存空間的地址可能是不相同的
如果一個程式中包含若干個函式,每個函式中的區域性變數的生存期並不等於整個程式的執行週期,它只是程式執行週期的一部分。在程式執行過程中,先後呼叫各個函式,此時會動態的分配和釋放儲存空間
在 C 語言中,每一個變數和函式都有兩個屬性:資料型別和資料的儲存類別。對資料型別,如整型、浮點型等。儲存類別指的是資料在記憶體中儲存的方式(如靜態儲存和動態儲存)
在定義和宣告變數和函式時,一般應同時指定其資料型別和儲存類別,也可以採用預設方式指定(即如果使用者不指定,系統會隱含的指定為某一種儲存類別)
C 的儲存類別包括4種:自動的(auto)、靜態的(static)、暫存器的(register)、外部的(extern)根據變數的儲存類別,可以知道變數的作用域和生存期
區域性變數的儲存類別
自動變數(auto 變數)
函式中的區域性變數,如果不專門宣告為 static
(靜態)儲存類別,都是動態的分配儲存空間的,資料儲存在動態儲存區中。函式中的形參和在函式中定義的區域性變數(包括在複合語句中定義的區域性變數),都屬於此類。在呼叫該函式時,系統會給這些變數分配儲存空間,在函式呼叫結束時就自動釋放這些儲存空間。因此這類區域性變數稱為自動變數。自動變數用關鍵字 auto
作儲存類別的宣告
auto 變數型別 變數名
實際上,關鍵字“auto”可以省略,不寫 auto 則隱含指定為“自動儲存類別”,它屬於動態儲存方式。程式中大多數變數屬於自動變數
auto 變數型別 變數名
等價於變數型別 變數名
靜態區域性變數(static 區域性變數)
有時希望函式中的區域性變數的值在函式呼叫結束後不消失而繼續保留原值,即其佔用的儲存單元不釋放,在下一次再呼叫該函式時,該變數已有值(就是上一次函式呼叫結束時的值)這時就應該指定該區域性變數為“靜態區域性變數”,用關鍵字 static
進行宣告
靜態區域性變數都屬於靜態儲存類別,在靜態儲存區內分配儲存單元。在程式整個執行期間都不釋放
自動變數(即動態區域性變數)屬於動態儲存類別,分配在動態儲存區空間而不在靜態儲存區空間,函式呼叫結束後釋放
對靜態區域性變數是在編譯時賦初值,即只賦初值一次,在程式執行時它已有初值。以後每次呼叫函式時不再重新賦初值而只是保留上次函式呼叫結束時的值
對自動變數賦初值,不是在編譯時進行的,而是在函式呼叫時進行的,每呼叫一次函式重新給一次初值,相當於執行一次賦值語句
如果在定義區域性變數時不賦初值的話:
對靜態區域性變數來說,編譯時自動賦初值0(對數值型變數)或空字元 '\0'
(對字元變數)
對自動變數來說,它的值是一個不確定的值,這是由於每次函式呼叫結束後儲存單元已釋放,下次呼叫時又重新另外分配儲存單元,而所分配的單元中的內容是不可知的
雖然靜態區域性變數在函式呼叫結束後仍然存在,但其他函式是不能引用它的。因為它是區域性變數,只能被本函式引用,而不能被其他函式引用
如果函式中的變數只被引用而不改變值,則定義為靜態區域性變數(同時初始化)比較方便,以免每次呼叫時重新賦值
用靜態儲存要多佔記憶體(長期佔用不釋放,而不能像動態儲存那樣一個儲存單元可以先後為多個變數使用,節約記憶體)而且降低了程式的可讀性,當呼叫次數多時往往弄不清楚靜態區域性變數的當前值是什麼。因此,若非必要,不要多用靜態區域性變數
暫存器變數(register 變數)
一般情況下,變數(包括靜態儲存方式和動態儲存方式)的值是存放在記憶體中的。當程式中用到哪一個變數的值時,由控制器發出指令將記憶體中該變數的值送到運算器中。經過運算器進行運算,如果需要存數,再從運算器將資料送到記憶體存放
如果有一些變數使用頻繁,則為存取變數的值要花費不少時間,為提高執行效率,允許將區域性變數的值存放在 CPU 中的暫存器中,需要用時直接從暫存器取出參加運算,不必再到記憶體中去存取。由於對暫存器的存取速度遠高於對記憶體的存取速度,因此這樣做可以提高執行效率,這總變數叫做暫存器變數,用關鍵字 register
作宣告
register 變數型別 變數名
由於現在的計算機的速度越來越快,效能越來越高,最佳化的編譯系統能夠識別使用頻繁的變數,從而自動的將這些變數放在暫存器中,而不需要程式設計者指定。因此,現在實際上用 register
宣告變數的必要性不大
三種區域性變數的儲存位置是不同的:
- 自動變數儲存在動態儲存區
- 靜態區域性變數儲存在靜態儲存區
- 暫存器儲存在 CPU 的暫存器中
全域性變數的儲存類別
全域性變數都是存放在靜態儲存區中的。因此它們的生存期是固定的,存在於程式的整個執行過程。一般來說,外部變數是在函式的外部定義的全域性變數,它的作用域是從變數的定義處開始,到本程式檔案的末尾。在此作用域內,全域性變數可以為程式中各個函式所引用
在一個檔案內擴充套件外部變數的作用域
如果外部變數不在檔案的開頭定義,其有效的作用範圍只限於定義處到檔案結束。在定義點之前的函式不能引用該外部變數。如果由於某種考慮,在定義點之前的函式需要引用該外部變數,則應該在引用之前用關鍵字 extern
對該變數作“外部變數宣告”,表示把該外部變數的作用域擴充套件到此位置。有了此宣告,就可以從“宣告”處起,合法的使用該外部變數
extern 變數型別 變數名
或extern 變數名
提倡將外部變數的定義放在引用它的所有函式之前,這樣子可以避免在函式中多加一個 extern
宣告
用 extern
宣告外部變數時,型別名可以寫也可以不寫,因為它不是定義變數,可以不指定型別,只虛寫出外部變數名即可
一個C程式可以由一個或多個源程式檔案組成。如果程式只由一個原始檔組成,此就是使用外部變數的方法
將外部變數的作用域擴充套件到其他檔案
一個C程式可以由一個或多個原始檔組成。如果一個程式包含兩個檔案,在兩個檔案中都要用到同一個外部變數 Num ,不能分別在兩個檔案中各自定義一個外部變數 Num ,否則在進行程式的連線時會出現“重複定義”的錯誤。正確的做法是:在任一檔案中定義外部變數 Num ,而在另一個檔案中用 extern
對 Num 作“外部變數宣告”,即“extern Num”。在編譯和連線時,系統會由此知道 Num 有“外部連線”,可以從別處找到已定義的外部變數 Num,並將在另一個檔案中定義的外部變數 Num 的作用域擴充套件到本檔案,在本檔案中可以合法的引用外部變數 Num
用這樣方法擴充套件全域性變數的作用域應十分慎重,因為在執行一個檔案中的操作時,可能會改變該全域性變數的值,會影響到另一個檔案中全域性變數的值,從而影響該檔案中函式的執行結果
在編譯時遇到 extern 時,先在本檔案中找外部變數的定義,如果找到,就在本檔案中擴充套件作用域;如果找不到,就在連線時從其他檔案中找外部變數的定義。如果從其他檔案中找到了,就將作用域擴充套件到本檔案中;如果找不到,就報錯
將外部變數的作用域限制在本檔案中
有時在程式設計中希望某些外部變數只限於被本檔案使用,而不能被其他檔案引用,此時可以在定義外部變數時加上一個 static 宣告
加上 static 宣告、只能用於本檔案的外部變數稱為靜態外部變數。在程式設計中,常由若干人分別完成各個模組,各人可以獨立的在其設計的檔案中使用相同的外部變數名而互不相干。只須在每個檔案中定義外部變數時加上 static 即可,這就為程式的模組化、通用性提供方便。如果已確認其他檔案不需要引用本檔案中的外部變數,就可以對本檔案中的外部變數都加上 static ,成為靜態外部變數,以免被其他檔案誤用。這就相當於把本檔案的外部變數對外界“遮蔽”起來,從其他檔案的角度看,這個靜態外部變數是“看不見,不能用”的。至於在各檔案中在函式內定義的區域性變數,本來就不能被函式外引用,更不能被其他檔案引用,因此是安全的
不要誤認為對外部變數加 static 宣告後採取靜態儲存方式(存放在靜態儲存區中),而不加 static 的是採取動態儲存(儲存在動態儲存區)。宣告區域性變數的儲存型別和宣告全域性變數的儲存型別的含義是不同的。對於區域性變數來說,什麼儲存型別的作用是指定變數儲存的區域(靜態儲存區或動態儲存區)以及由此產生的生存期的問題,而對於全域性變數來說,由於都是在編譯時分配記憶體的,都存放在靜態儲存區,宣告儲存型別的作用是變數作用域的擴充套件問題
用 static 宣告一個變數的作用:
- 對區域性變數用 static 宣告,把它分配在靜態儲存區,該變數在整個程式執行期間不釋放,其所分配的空間始終存在
- 對全域性變數用 static 宣告,則該變數的作用域只限於本檔案模組(即被宣告的檔案中)
用 auto,register 和 static 宣告變數時,是在定義變數的基礎上加上這些關鍵字,而不能單獨使用
儲存類別小結
對一個資料的定義,需要指定兩種屬性:資料型別和儲存類別,分別使用兩個關鍵字
可以用 extern 宣告已定義的外部變數
從作用域角度分,有區域性變數和全域性變數,它們採用的儲存類別如下:
從變數存在的時間(生存期)來區分,有動態儲存和靜態儲存兩種型別。靜態儲存是程式整個執行時間都存在,而動態儲存則是呼叫函式時臨時分配單元。具體如下:
從變數值存放的位置區分:
對一個變數的屬性可以從兩個方面分析,一是變數的作用域,一是變數值存在時間的長短,即生存期。前者是從空間的角度,後者是從時間的角度。二者有聯絡但不是同一回事
如果一個變數在某個檔案或函式範圍內是有效的,就稱該範圍為該變數的作用域,在此作用域內可以引用該變數,在專業書中稱變數在次作用域內“可見”,這種性質稱為變數的可見性
如果一個變數值在某一時刻是存在的,則認為這一時刻屬於該變數的生存期,或稱該變數在此時刻“存在”
變數儲存類別 | 函式內 | 函式外 | ||
作用域 | 存在性 | 作用域 | 存在性 | |
自動變數和暫存器變數 | √ | √ | × | × |
靜態區域性變數 | √ | √ | × | √ |
靜態外部變數 | √ | √ | √(只限本檔案) | √ |
外部變數 | √ | √ | √ | √ |
自動變數和暫存器變數在函式內外的“可見性”和“存在性”一致,即離開函式後,值不能被引用,值也不存在
靜態外部變數和外部變數在函式內外的“可見性”和“存在性”一致,即離開函式後,變數值仍存在,且可被引用
靜態區域性變數在函式內外的“可見性”和“存在性”不一致,離開函式後,變數值存在,但不能被引用
static 對區域性變數和全域性變數的作用不同:
- 對區域性變數來說,它使變數由動態儲存方式改變為靜態儲存方式
- 對全域性變數來說,它使變數區域性化(區域性於本檔案),但仍為靜態儲存方式
從作用域角度看,凡有 static 宣告的,其作用域都是侷限的,或者是侷限於本函式內(靜態區域性變數),或者是侷限於本檔案內(靜態外部變數)