做用域、連接屬性和存儲類型

最近在讀《程序員的自我修養——連接、裝載與庫》,感受本身當初學習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關鍵字:

    • 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,這對咱們在深度嵌套代碼塊中引用全局變量提供了一個途徑。
相關文章
相關標籤/搜索