最近在讀《程序員的自我修養——連接、裝載與庫》,感受本身當初學習C的時候,對extern、static等關鍵字瞭解不是特別清晰,所以重溫了一遍《C和指針》中關於做用域、連接屬性和存儲類型的相關部分,加上了本身的理解,用博客記錄一下。程序員
當變量在程序的某個部分被聲明時,它只有在程序的必定區域才能被訪問。函數
這個區域由標識符的做用域決定,標識符的做用域就是程序中該標識符能夠被使用的區域。學習
據我所學,編譯原理中有講到,檢查變量的做用域是否合乎規則,是在編譯中的語義分析時查看的。優化
編譯器能夠確認4種不一樣類型的做用域——文件做用域、函數做用域、代碼塊做用域和原型做用域。標識符聲明的位置決定了它的做用域。指針
代碼塊做用域調試
位於一堆花括號之間的全部語句稱爲一個代碼塊,任何在代碼塊的開始位置聲明的標識符具備代碼塊做用域。代表他們能夠被這個代碼塊中的全部語句訪問。code
下圖中的a、b、c、d和arg均具備代碼塊做用域。blog
/* 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關鍵字:
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關鍵字