參考文章:
1.2.
3.1.cpp
int n = 1;
void f()
2.cpp
extern int n;
void g()
未解決符號表和匯出符號表
,分別告訴鏈結器
自己需要什麼和能夠提供什麼
位址重定向表是為了
首先決定各個目標檔案在最終可執行檔案裡的位置。
然後訪問所有目標檔案的位址重定向表,對其中記錄的位址進行重定向(即加上該編譯
單元實際在可執行檔案裡的起始位址)。
然後遍歷所有目標檔案的未解決符號表,並且在所有的匯出符號表裡查詢匹配的符號,並在未解決符號表中所記錄的位置上
填寫實際的位址(也要加上擁有該符號定義的編譯單元實際在可執行檔案裡的起始位址)。
最後把所有的目標檔案的內容寫在各自的位置上,再作一些別的工作,一
個可執行檔案就出爐了。
unresolved external link..
這個很顯然,是鏈結器發現乙個未解決符號,但是在匯出符號表裡沒有找到對應的項。
解決方案麼,當然就是在某個編譯單元裡提供這個符號的定義就行了。(注意,這個符號可以是乙個變數,也可以是乙個函式),也可以看看是不是有什麼該鏈結的檔案沒有鏈結
duplicated external simbols...
這個則是匯出符號表裡出現了重複項,因此鏈結器無法確定應該使用哪乙個。這可能是使用了重複的名稱,也可能有別的原因。
我們再來看看
c/c++
語言裡針對這一些而提供的特性:
extern:
這是告訴編譯器,這個符號在別的編譯單元裡定義,也就是要把這個符號放到未解決符號表裡去。(外部鏈結)
static:
如果該關鍵字位於全域性函式或者變數的宣告的前面,表明該編譯單元不匯出這個函式/變數的符號。因此無法在別的編譯單元裡使用。(內部鏈結)。如果是
static
區域性變數,則該變數的儲存方式和全域性變數一樣,但是仍然不匯出符號。
const
變數,預設內部鏈結。(可以通過新增
extern
和static
改變鏈結屬性)
外部鏈結的符號,可以在整個程式範圍內使用(因為匯出了符號)。但是同時要求其他的編譯單元不能匯出相同的符號(不然就是
duplicated external simbols)
內部鏈結的符號,不能在別的編譯單元內使用。但是不同的編譯單元可以擁有同樣名稱的內部鏈結符號。
*********
為什麼標頭檔案裡一般只可以有宣告不能有定義*********
標頭檔案可以被多個編譯單元包含,如果標頭檔案裡有定義,那麼每個包含這個標頭檔案的編譯單元就都會對同乙個符號
進行定義,如果該符號為外部鏈結,則會導致
duplicated external simbols
。因此如果標頭檔案裡要定義,必須保證定義的符號只能具有內部鏈結。
*********為什麼常量預設為內部鏈結,而變數不是*********
這就是為了
,能夠在標頭檔案裡如
const int n = 0
這樣的定義常量。由於常量是唯讀的,因此即使每個編譯單元都擁有乙份定義也沒有關係。如果乙個定義於標頭檔案裡的變數擁有內部鏈結,那麼如果出現多個編譯
單元都定義該變數,則其中乙個編譯單元對該變數進行修改,不會影響其他單元的同一變數,會產生意想不到的後果。
*********
為什麼函式預設是外部鏈結*********
雖然函式是唯讀的,但是和變數不同,函式在**編寫的時候非常容易變化,如果函式預設具有內部鏈結,則人們會傾向於把函式定義在標頭檔案裡,那麼一旦函式
被修改,所有包含了該標頭檔案的編譯單元都要被重新編譯。另外,函式裡定義的靜態區域性變數也將被定義在標頭檔案裡。
為什麼類的靜態變數不可以就地初始化
:所謂就地初始化就是類似於這樣的情況:
class a
;不允許這樣做得原因是,
由於
class
的宣告通常是在標頭檔案裡,如果允許這樣做,其實就相當於在標頭檔案裡定義了乙個非
const
變數。在
c++裡,標頭檔案定義乙個
const
物件會怎麼樣:
一般不會怎麼樣,這個和
c裡的在標頭檔案裡定義
const int
一樣,每乙個包含了這個標頭檔案的編譯單元都會定義這個物件。但由於該物件是
const
的,所以沒什麼影響。但是:有
2種情況可能破壞這個局面:
1。如果涉及到對這個
const
物件取位址並且依賴於這個位址的唯一性,那麼在不同的編譯單元裡,取到的位址可以不同。(但一般很少這麼做)
2。如果這個物件具有
mutable
的成員變數,某個編譯單元對其進行修改,則同樣不會影響到別的編譯單元。
為什麼類的靜態常量也不可以就地初始化:
???????
因為這相當於在標頭檔案裡定義了
const
物件。作為例外,
int/char
等可以進行就地初始化,是因為這些變數可以直接被優化為立即數,就和巨集一樣。
內聯函式:
c++裡的內聯函式由於類似於乙個巨集,因此不存在鏈結屬性問題。
為什麼公共使用的內聯函式要定義於標頭檔案裡:
因為編譯時編譯單元之間互相不知道,如果內聯函式被定義於
.cpp
檔案中,編譯其他使用該函式的編譯單元的時候沒有辦法找到函式的定義,因此無法對函式進行展開。所以說如果內聯函式定義於
.cpp
檔案裡,那麼就只有這個
cpp檔案可以是用這個函式。
標頭檔案裡內聯函式被拒絕會怎樣:
如果定義於標頭檔案裡的內聯函式被拒絕,那麼編譯器會自動在每個包含了該標頭檔案的編譯單元裡定義這個函式並且不匯出符號。
如果被拒絕的內聯函式裡定義了靜態區域性變數,這個變數會被定義於何處:
早期的編譯器會在每個編譯單元裡定義乙個,並因此產生錯誤的結果,較新的編譯器會解決這個問題,手段未知。
為什麼export
關鍵字沒人實現:
export
要求編譯器跨編譯單元查詢函式定義,使得編譯器實現非常困難。
C 編譯 執行原理
關於編譯與記憶體的關係,以及執行時記憶體的劃分 1 所謂在編譯期間分配空間指的是靜態分配空間 相對於用new動態申請空間 如全域性變數或靜態變數 包括一些複雜型別的 常量 它們所需要的空間大小可以明確計算出來,並且不會再改變,因此它們可以直接存放在可執行檔案的特定的節裡 而且 包含初始化的值 程式執...
C語言編譯原理
c語言編譯過程詳解 c語言的編譯鏈結過程是要把我們編寫的乙個c程式 源 轉換成可以在硬體上執行的程式 可執行 需要進行編譯和鏈結。編譯就是把文字形式源 翻譯為機器語言形式的目標檔案的過程。鏈結是把目標檔案 作業系統的啟動 和用到的庫檔案進行組織形成最終生成可執行 的過程。整個 的編譯過程分為編譯和鏈...
C 編譯鏈結原理
靜態語義 通常包含宣告和型別的匹配,型別的轉換等。動態語義 像比如0作為除數就是乙個執行期的語義錯誤。彙編階段主要是將編譯階段生成的彙編 程式設計二進位制的機器語言並生成.o檔案,稱為目標檔案,o檔案屬於二進位制檔案。主要做的就是翻譯指令。在這一階段有生成中間檔案,中間檔案經過 生成器優化 比如選擇...