最近在讀《程式員的自我修養——鏈接、裝載與庫》,感覺自己當初學習C的時候,對extern、static等關鍵字了解不是特別清晰,因此重溫了一遍《C和指標》中關于作用域、鏈接屬性和存盤型別的相關部分,加上了自己的理解,用博客記錄一下,
作用域
當變數在程式的某個部分被宣告時,它只有在程式的一定區域才能被訪問,
這個區域由識別符號的作用域決定,識別符號的作用域就是程式中該識別符號可以被使用的區域,
據我所學,編譯原理中有講到,檢查變數的作用域是否合乎規則,是在編譯中的語意分析時查看的,
編譯器可以確認4種不同型別的作用域——檔案作用域、函式作用域、代碼塊作用域和原型作用域,識別符號宣告的位置決定了它的作用域,
-
代碼塊作用域
位于一堆花括號之間的所有陳述句稱為一個代碼塊,任何在代碼塊的開始位置宣告的識別符號具有代碼塊作用域,表明他們可以被這個代碼塊中的所有陳述句訪問,
下圖中的a、b、c、d和arg均具有代碼塊作用域,
/* main.c */ #include <stdio.h> int g; int func(int x); int main(int argc, char* argv[]) { int a; int b; a = 5; { int c; int a; //隱藏外部的a,外層的那個識別符號將無法在內層代碼塊中通過名字訪問, c = 5; a = 10; printf("%d", c + a); //列印結果:15,而不是10 } { int d; func(d); } } int func(int arg) { // }注:我們應當避免在嵌套的代碼塊中出現相同的變數名,因為并沒有很好的理由使用這種技巧,他們只會在程式的除錯或維護期間引起混淆,
-
檔案作用域
任何在所有代碼塊之外宣告的識別符號都具有檔案作用域(file scope),他表示這些識別符號從他們的宣告之處直到他所在的源檔案結尾處都是可以訪問的,g、func和main都具有檔案作用域,這也就是為什么我們要將func的宣告單獨寫在main函式前,就是為了main可以呼叫func函式,否則main是不可以訪問到func函式的,
-
原型作用域
原型作用域只適用于在函式原型中宣告的引數名,如func宣告陳述句中的x,
-
函式作用域
只適用于陳述句標簽,陳述句標簽用于goto陳述句,
后兩種作用域非常非常不常見,因此我們應當把關注點放在前兩個作用域上面,
鏈接屬性
識別符號的鏈接屬性(Linkage)決定如何處理在不同檔案中出現的識別符號,識別符號的作用域與它的鏈接屬性有關,但這兩個屬性并不相同,
/* main.c */
#include <stdio.h>
int g;
int func(int x);
int main(int argc, char* argv[]) {
int a;
int b;
a = 5;
{
int c;
int a; //隱藏外部的a,外層的那個識別符號將無法在內層代碼塊中通過名字訪問,
c = 5;
a = 10;
printf("%d", c + a); //列印結果:15,而不是10
}
{
int d;
func(d);
}
}
int func(int arg) {
//
}
-
external
屬于external鏈接屬性的識別符號不管宣告多少次,位于幾個源檔案都表示同一個物體,
預設情況下,宣告在任何代碼塊之外的變數或函式(即具有檔案作用域)具有external鏈接屬性,其余都為none,代碼中g、func和main鏈接屬性都是external,其余的變數鏈接屬性均為none,
extern關鍵字:
- extern關鍵字為一個識別符號指定external鏈接屬性,
- 對于檔案作用域即已經是extern鏈接屬性的變數,extern關鍵字是可選的
- extern關鍵字用于源檔案中一個識別符號的第一次宣告時,它指定該識別符號具有extern鏈接屬性,但是如果該識別符號用于該識別符號的第2次或以后的宣告,他并不會更改由第一次宣告所指定的鏈接屬性,
-
internal
具有internal鏈接屬性的識別符號在同一個源檔案內的所有宣告都指同一個個體,但位于不同源檔案的多個宣告則分屬不同的物體,
如果某個宣告在正常情況下具有external鏈接屬性,在他面前加上static關鍵字,可以使他的鏈接屬性變為internal,例如如果g的宣告為
static int g;,那么變數g就變為源檔案私有,其他源檔案如果要鏈接g的變數,參考的是另一個不同的變數,類似的,函式宣告也可以是static,如static int func(int x);,static只有對預設鏈接屬性為external的宣告才有改變鏈接屬性的效果,
-
none
沒有鏈接屬性的識別符號(none)總是被當作單獨的個體,也就是說該識別符號的多個宣告被當作獨立不同的個體,
存盤型別
變數的存盤型別(storage class)是指存盤變數值的記憶體型別,變數的存盤型別決定變數何時創建、何時銷毀以及它的值將保持多久,有三個地方可以用于存盤變數:
-
普通記憶體
凡是在任何代碼塊之外宣告的變數(具有檔案作用域、external鏈接屬性)總是存盤于靜態記憶體,這類變數稱為靜態變數,放在二進制檔案的.data段或bss段中,
靜態變數在程式運行之前創建,在程式的整個執行期間始終存在,他始終保持原先的值,除非給他附一個不同的值或程式結束,
-
運行時堆疊
在代碼內部宣告的變數的預設存盤型別是自動的,也就是說他存盤于堆疊中,稱為自動變數,
如果給他加上關鍵字static,可以使他的存盤型別從自動變為靜態(放在.data段或.bss段中),具有靜態存盤型別的變數在整個程式執行程序中一直存在,而不僅僅在宣告它的代碼塊的執行時存在,注意,修改變數的存盤型別并不表示修改該變數的作用域,他雖然始終存在,但是還是只能在該代碼塊內宣告過后,按名字訪問,
-
硬體暫存器
用于自動變數的宣告,提醒他們應該存盤于機器的硬體暫存器,而不是記憶體中,這類變數稱為暫存器變數,但是編譯器并不一定要理睬register關鍵字,也就是說不是你在變數前加了register關鍵字,這個變數最后就被存盤于機器的硬體暫存器里面了,還是要看編譯器的”心情“的,即取決于編譯器的優化方案??,
注:register變數是不提供地址的哦,

淺談初始化
初始化靜態變數不需要額外的時間和開銷,變數將會得到正確的值,如果不顯示地指定其初始值,靜態變數將初始化為0,因為靜態變數直接存在.data段或.bss段里面,在生成目標檔案時已經被編譯器寫進去了,所以運行時肯定不花時間,
自動變數的初始化需要更多開銷因為當程式鏈接時還無法判斷自動變數的存盤位置,事實上,函式的區域變數在函式的每次呼叫中可能占據不同的位置,因此基于這個理由,自動變數沒有預設的初始值,而顯式的初始化將在代碼塊的起始處插入一條隱式的賦值陳述句,這里的隱式我認為就是代碼段中插入了一條賦值陳述句如mov [ebp -4] , value,這樣的話就造成初始化和先宣告后賦值效率并無提高,只有風格之差,
Static和Extern
-
當用于不同的背景關系環境時,static關鍵字具有不同的意思,
- 用于具有檔案作用域的變數或函式時,Static關鍵字可以改變他們的鏈接屬性,從external改為internal,但識別符號的作用域和存盤型別不受影響,函式照樣放在.text段中,全域變數根據是否初始化放在.data段或.bss段中,
- 用于具有代碼塊作用域的變數時,其鏈接屬性為none,static不改變其鏈接屬性,而是修改變數的存盤型別,從自動變數改為靜態變數,作用域也不受影響,
-
extern關鍵字
- 用于具有檔案作用域的變數或函式時,extern關鍵字是可選的,因為本身他們就具有external鏈接屬性,然而,如果你在其中一個地方定義變數,并在使用這個變數的其他源檔案的宣告中添加extern關鍵字,可以使讀者更好地了解你的意圖,
- 用于具有代碼段作用域的區域變數時,extern關鍵字可以修改變數的鏈接屬性從none到external,這對我們在深度嵌套代碼塊中參考全域變數提供了一個途徑,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/259393.html
標籤:其他
