C/C++編譯問題、static問題、const問題

C/C++編譯和鏈接過程詳解 (重定向表,導出符號表,未解決符號表)

編譯器把一個cpp編譯爲目標文件的時候,除了要在目標文件裏寫入cpp裏包含的數據和代碼,還要至少提供3個表:未解決符號表導出符號表地址重定向表

  1. 未解決符號表提供了所有在該編譯單元裏引用但是定義並不在本編譯單元裏的符號及其出現的地址。

  2. 導出符號表提供了本編譯單元具有定義,並且願意提供給其他編譯單元使用的符號及其地址。

  3. 地址重定向表提供了本編譯單元所有對自身地址的引用的記錄。

  鏈接器進行鏈接的時候,首先決定各個目標文件在最終可執行文件裏的位置。然後訪問所有目標文件的地址重定向表,對其中記錄的地址進行重定向(即加上該編譯 單元實際在可執行文件裏的起始地址)。再則遍歷所有目標文件的未解決符號表,並且在所有的導出符號表裏查找匹配的符號,並在未解決符號表中所記錄的位置上 填寫實際的地址(也要加上擁有該符號定義的編譯單元實際在可執行文件裏的起始地址)。最後把所有的目標文件的內容寫在各自的位置上,再做一些別的工作,一 個可執行文件就出爐了。

NOTE THAT

extern:這是告訴編譯器,這個符號在別的編譯單元裏定義,也就是要把這個符號放到未解決符號表裏去。(外部鏈接)

static:如果該關鍵字位於全局函數或者變量的聲明的前面,表明該編譯單元不導出這個函數/變量的符號。因此無法在別的編譯單元裏使用。(內部鏈接)。如果是static局部變量,則該變量的存儲方式和全局變量一樣,但是仍然不導出符號。

默認鏈接屬性:對於函數和變量,模認外部鏈接,對於const變量,默認內部鏈接。(可以通過添加extern和static改變鏈接屬性)

外部鏈接的利弊:外部鏈接的符號,可以在整個程序範圍內使用(因爲導出了符號)。但是同時要求其他的編譯單元不能導出相同的符號(不然就是duplicated external simbols)

內部鏈接的利弊:內部鏈接的符號,不能在別的編譯單元內使用。但是不同的編譯單元可以擁有同樣名稱的內部鏈接符號。

爲什麼頭文件裏一般只可以有聲明不能有定義:頭文件可以被多個編譯單元包含,如果頭文件裏有定義,那麼每個包含這個頭文件的編譯單元就都會對同一個符號 進行定義,如果該符號爲外部鏈接,則會導致duplicated external simbols。因此如果頭文件裏要定義,必須保證定義的符號只能具有內部鏈接。

爲什麼常量默認爲內部鏈接,而變量不是:

這就是爲了能夠在頭文件裏如const int n = 0這樣的定義常量。由於常量是隻讀的,因此即使每個編譯單元都擁有一份定義也沒有關係。如果一個定義於頭文件裏的變量擁有內部鏈接,那麼如果出現多個編譯 單元都定義該變量,則其中一個編譯單元對該變量進行修改,不會影響其他單元的同一變量,會產生意想不到的後果。 

爲什麼函數默認是外部鏈接:

雖然函數是隻讀的,但是和變量不同,函數在代碼編寫的時候非常容易變化,如果函數默認具有內部鏈接,則人們會傾向於把函數定義在頭文件裏,那麼一旦函數 被修改,所有包含了該頭文件的編譯單元都要被重新編譯。另外,函數裏定義的靜態局部變量也將被定義在頭文件裏。 

爲什麼類的靜態變量不可以就地初始化:所謂就地初始化就是類似於這樣的情況:

class A 
{ 
     static char msg[] = "aha"; 
}; 
不允許這樣做得原因是,由於class的聲明通常是在頭文件裏,如果允許這樣做,其實就相當於在頭文件裏定義了一個非const變量。 

在C++裏,頭文件定義一個const對象會怎麼樣:

一般不會怎麼樣,這個和C裏的在頭文件裏定義const int一樣,每一個包含了這個頭文件的編譯單元都會定義這個對象。但由於該對象是const的,所以沒什麼影響。但是:有2種情況可能破壞這個局面: 

1。如果涉及到對這個const對象取地址並且依賴於這個地址的唯一性,那麼在不同的編譯單元裏,取到的地址可以不同。(但一般很少這麼做)

2。如果這個對象具有mutable的變量,某個編譯單元對其進行修改,則同樣不會影響到別的編譯單元。

爲什麼類的靜態常量也不可以就地初始化:

因爲這相當於在頭文件裏定義了const對象。作爲例外,int/char等可以進行就地初始化,是因爲這些變量可以直接被優化爲立即數,就和宏一樣。 

內聯函數:

    C++裏的內聯函數由於類似於一個宏,因此不存在鏈接屬性問題。 

爲什麼公共使用的內聯函數要定義於頭文件裏:

    因爲編譯時編譯單元之間互相不知道,如果內聯函數被定義於.cpp文件中,編譯其他使用該函數的編譯單元的時候沒有辦法找到函數的定義,因此無法對函數進行展開。所以說如果內聯函數定義於.cpp文件裏,那麼就只有這個cpp文件可以是用這個函數。 

頭文件裏內聯函數被拒絕會怎樣:

    如果定義於頭文件裏的內聯函數被拒絕,那麼編譯器會自動在每個包含了該頭文件的編譯單元裏定義這個函數並且不導出符號。 

如果被拒絕的內聯函數裏定義了靜態局部變量,這個變量會被定義於何處:

    早期的編譯器會在每個編譯單元裏定義一個,並因此產生錯誤的結果,較新的編譯器會解決這個問題,手段未知。 

爲什麼export關鍵字沒人實現:

    export要求編譯器跨編譯單元查找函數定義,使得編譯器實現非常困難。

C中static的常見作用

C程序一直由下列部分組成:

1)正文段——CPU執行的機器指令部分;一個程序只有一個副本;只讀,防止程序由於意外事故而修改自身指令;

2)初始化數據段(數據段)——在程序中所有賦了初值的全局變量,存放在這裏。

3)非初始化數據段(bss段)——在程序中沒有初始化的全局變量;內核將此段初始化爲0。

4)棧——增長方向:自頂向下增長;自動變量以及每次函數調用時所需要保存的信息(返回地址;環境信息)。

5)堆——動態存儲分。

1.全局靜態變量

在全局變量之前加上關鍵字static,全局變量就被定義成爲一個全局靜態變量。

1)內存中的位置:靜態存儲區(靜態存儲區在整個程序運行期間都存在)

2)初始化:未經初始化的全局靜態變量會被程序自動初始化爲0(自動對象的值是任意的,除非他被顯示初始化)

3)作用域:全局靜態變量在聲明他的文件之外是不可見的。準確地講從定義之處開始到文件結尾。

優點
定義全局靜態變量的好處:

(1)不會被其他文件所訪問,修改

(2)其他文件中可以使用相同名字的變量,不會發生衝突。

2.局部靜態變量

在局部變量之前加上關鍵字static,局部變量就被定義成爲一個局部靜態變量。

1)內存中的位置:靜態存儲區

2)初始化:未經初始化的全局靜態變量會被程序自動初始化爲0(自動對象的值是任意的,除非他被顯示初始化)

3)作用域:作用域仍爲局部作用域,當定義它的函數或者語句塊結束的時候,作用域隨之結束。

NOTE THAT

當static用來修飾局部變量的時候,它就改變了局部變量的存儲位置,從原來的棧中存放改爲靜態存儲區。但是局部靜態變量在離開作用域之後,並沒有被銷燬,而是仍然駐留在內存當中,直到程序結束,只不過我們不能再對他進行訪問。

當static用來修飾全局變量的時候,它就改變了全局變量的作用域(在聲明他的文件之外是不可見的),但是沒有改變它的存放位置,還是在靜態存儲區中。

3. 靜態函數

在函數的返回類型前加上關鍵字static,函數就被定義成爲靜態函數。

函數的定義和聲明默認情況下是extern的,但靜態函數只是在聲明他的文件當中可見,不能被其他文件所用。

定義靜態函數的好處:

(1)其他文件中可以定義相同名字的函數,不會發生衝突

(2)靜態函數不能被其他文件所用。 存儲說明符auto,register,extern,static,對應兩種存儲期:自動存儲期和靜態存儲期。 auto和register對應自動存儲期。具有自動存儲期的變量在進入聲明該變量的程序塊時被建立,它在該程序塊活動時存在,退出該程序塊時撤銷。

關鍵字extern和static用來說明具有靜態存儲期的變量和函數。用static聲明的局部變量具有靜態存儲持續期(static storage duration),或靜態範圍(static extent)。雖然他的值在函數調用之間保持有效,但是其名字的可視性仍限制在其局部域內。靜態局部對象在程序執行到該對象的聲明處時被首次初始化。

4.由於static變量的以上特性,可實現一些特定功能。
1)統計次數功能
聲明函數的一個局部變量,並設爲static類型,作爲一個計數器,這樣函數每次被調用的時候就可以進行計數。這是統計函數被調用次數的最好的辦法,因爲這個變量是和函數息息相關的,而函數可能在多個不同的地方被調用,所以從調用者的角度來統計比較困難。

5.不同類型變量的區別
與auto類型(普通)局部變量相比, static局部變量有三點不同

1) 存儲空間分配不同

auto類型分配在棧上, 屬於動態存儲類別, 佔動態存儲區空間, 函數調用結束後自動釋放, 而static分配在靜態存儲區, 在程序整個運行期間都不釋放. 兩者之間的作用域相同, 但生存期不同.

2)static局部變量在所處模塊在初次運行時進行初始化工作, 且只操作一次

3)對於局部靜態變量, 如果不賦初值, 編譯期會自動賦初值0或空字符, 而auto類型的初值是不確定的. (對於C++中的class對象例外, class的對象實例如果不初始化, 則會自動調用默認構造函數, 不管是否是static類型)

特點: static局部變量的”記憶性”與生存期的”全局性”
所謂”記憶性”是指在兩次函數調用時, 在第二次調用進入時, 能保持第一次調用退出時的值.

6.外部靜態變量/函數

在C中 static有了第二種含義:用來表示不能被其它文件訪問的全局變量和函數。但爲了限制全局變量/函數的作用域, 函數或變量前加static使得函數成爲靜態函數。但此處“static”的含義不是指存儲方式,而是指對函數的作用域僅侷限於本文件(所以又稱內部函數)

注意此時, 對於外部(全局)變量, 不論是否有static限制, 它的存儲區域都是在靜態存儲區, 生存期都是全局的. 此時的static只是起作用域限制作用, 限定作用域在本模塊(文件)內部.

使用內部函數的好處是:不同的人編寫不同的函數時,不用擔心自己定義的函數,是否會與其它文件中的函數同名。


C/C++ const

CONST關鍵字至少有下列n個作用

(1)欲阻止一個變量被改變,可以使用const關鍵字。在定義該const變量時,通常需要對它進行初始化,因爲以後就沒有機會再去改變它了
(2)對指針來說,可以指定指針本身爲const,也可以指定指針所指的數據爲const,或二者同時指定爲const;
(3)在一個函數聲明中,const可以修飾形參,表明它是一個輸入參數,在函數內部不能改變其值
(4)對於類的成員函數,若指定其爲const類型,則表明其是一個常函數,不能修改類的成員變量
(5)對於類的成員函數,有時候必須指定其返回值爲const類型,以使得其返回值不爲“左值”。

REFERENCE

  1. C/C++編譯和鏈接過程詳解 (重定向表,導出符號表,未解決符號表)
  2. C中static的常見作用
  3. 關鍵字static/const的作用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章