鏈結,就是將不同部分的**和資料收集和組合成為乙個單一檔案的過程,這個檔案可被載入到儲存器中執行。鏈結可以執行於編譯時(compile time) ,也就是源**被翻譯成機器**時(eg.普通的鏈結器鏈結,以及靜態鏈結庫,由靜態鏈結器鏈結);也可以執行於載入時(例如動態鏈結庫的載入時鏈結);也可以執行於執行時(run time)(動態鏈結庫在應用程式中鏈結)。
1. 編譯器驅動程式----首先,看一下程式在完成後系統的一些動作(以main.cpp為例,一下均以main.xx為例)
main.cpp 經過 c預處理器 ascii碼中間檔案 main.i
main.i 經過 c編譯器 ascii組合語言檔案 main.s
main.s 經過 彙編器 可重定位目標檔案 main.o
main.o 經過 鏈結器 可執行的目標檔案 假設為p
系統shell 呼叫載入器(loader), 拷貝可執行檔案p 中的**和資料到儲存器,然後控制轉移到找個程式的開頭執行找個程式。
2. 鏈結器的動作
unix 鏈結器 以一組可重定位的目標檔案和命令列引數作為輸入,生成乙個可以完全鏈結的可以載入和執行的可執行目標檔案。
它所完成的動作有:
a. 符號解析(symbol resolution): 將每個符號引用和乙個符號定義聯絡起來。符號:程式中的函式名、變數名等。
b. 重定位(relocation): 編譯器和彙編器生成的是從位址零開始的**和資料節。鏈結器把每個符號定義與儲存器位置聯絡起來,並修改符號引用,使之指向這個儲存器位置,從而重定位這些符號。---即,為符號分配儲存位置,並用找個位址代替程式中對符號的引用。
3. 符號解析
對於每個輸入的檔案,彙編器會產生乙個可重定位的目標檔案main.o,unix系統中為elf格式。這個檔案中由不同的節構成,常用的有如下幾個:
.text **段,存放編譯後的**
.data 資料段,存放程式檔案中初始化的全域性變數
.bss 資料段,沒有空間,名義上存放未初始化的全域性變數,為提高效率而設
.systab 符號表,存放在程式中定義或引用的全域性變數或函式符號,並有乙個資料結構維護,用於符號解析
.rel.text 重定位**段(即函式) - 存放 需要重定位的符號的重定位表目,乙個資料結構維護,存放重定位資訊。
.rel.data 重定位資料段(即全域性變數) 內容同上
(詳見--深入理解計算機系統一書 page 465)
符號解析,即根據.systab中的符號表,對每個引用與它輸入的可重定位目標檔案的符號表中的乙個確定的符號定義進行聯絡。如果有的符號引用找不到對應的定義,則會產生錯誤,undefined reference!!
若同時出現多個符號定義,則函式和已初始化的全域性變數為強符號,未初始化的全域性變數為弱符號,鏈結器按如下方式進行符號解析
1. 不允許有多個強符號
2. 若乙個強符號,若干個弱符號,則選擇強符號
3. 若 多個弱符號,則從其中選擇乙個。
需要特別注意,多個定義中所引起的,儲存器越界錯誤,見 p471
4. 重定位
彙編器在處理main.s時候,只要遇到最終位置沒有確定的符號,即全域性變數或函式,就會在rel.text 或者 rel.data中生成乙個幫助鏈結器進行重定位的表目,用乙個資料結構進行維護,在鏈結器進行鏈結時候,就會利用這些資訊對符號進行重定位。具體見 p 477
5. 靜態鏈結庫
靜態鏈結庫,是一些已經編譯好的函式**模組,可以在鏈結時和main.s一起進行鏈結,那麼源程式檔案main.cpp中就可以使用靜態鏈結庫已經寫好的函式或者變數,當然肯定是函式居多。
在main.o中沒有找到定義的符號引用,鏈結器就會在一起鏈結的靜態鏈結庫中尋找,如果找到,就會把相應的模組**拷貝到最終要生成的可執行檔案中,若沒找到,則生成錯誤。
需要注意的是,鏈結器只拷貝它需要的**,而不是所有的**,所以,在鏈結時候,一定要考慮好原始檔和靜態鏈結庫之間的依賴關係,按順序鏈結,否則,可能會出錯。
6. 動態鏈結庫(共享庫)
這是個很有用的東東,即可以在應用程式中在執行時候載入(需要特殊的標頭檔案和函式),也可以在載入時候完成鏈結。它的特性是,整個儲存器中是獨乙份的,只有乙個副本供使用,即多個程序可以共享整個玩意兒。即,它不需要像靜態鏈結庫那樣拷貝到最後的可執行檔案中,動態載入到儲存器就可以,大大節省了儲存空間。
但有乙個問題,即,如果共享庫中有使用全域性資料或者呼叫函式,在重定位階段需要修改**為所使用符號的絕對位址,程式才能正常執行,但由於不同的程序對應著不同的共享庫載入位址,也即,不同的程序會對**進行不同的修改,這樣的話,因為只有乙個副本,那多程序共享就行不通的啦。因此,產生乙個這樣的機制,來解決整個問題,使得**執行與共享庫的載入位址沒有直接的關係。即pic(position independent code)
首先,我們來看,**只有乙份,也就是**段實體地址唯一,不同程序載入時候,程序間所載入的虛擬位址不同。但是資料段的實體地址肯定是不同的,**段不能修改,我們可以修改資料段。這個過程得益於乙個邏輯,即資料段總是分配為緊隨**段後面(虛擬位址),即,資料段和**段之間的相對位址是不會隨載入位址改變而改變的。
於是,我們在資料段開頭的位置,維護乙個got表,它為每個符號定義了乙個重定位表目,載入時,動態鏈結器會重定位got表中的表目,使得它包含正確的絕對位址。那麼,我們在程式中只使用資料段與**指令之間的相對位址就可以了,然後從got表中得到最終的正確的絕對位址,那麼就實現了position independent. 實現細節見 p 488
動態鏈結器這一段,是我在看了深入理解計算機系統對於pic自己的一些解讀,尤其是實體地址和虛擬位址那一塊兒,由於那一章還沒有看到,也不知道對不對,但是,我覺得這樣可以解釋通!!
關於軟硬鏈結那些事兒
軟連線 硬鏈結,那麼啥是鏈結呢?小編覺得鏈結簡單來說就是一種共享的方式,比如我們去逛超市,超市的入口就是 鏈結 在linux中,鏈結又分為軟連線和硬鏈結,想要了解他倆,就得先了解幾個基本概念。一 inode 我們知道檔案包含資料和元資料,它的資料都存在block 塊 中,那它的元資料,比如說它的檔案...
指標那些事兒
1.野指標 也叫懸擺指標,迷失指標 野指標是導致bug的罪魁禍首之一。對指標呼叫delete後 釋放掉了它指向的記憶體,野指標還是指向原來的位址 如果沒有重新賦值就使用它,將導致難以預料的後果。因為此時操作野指標,它指向的記憶體位址可能已經分配給其他變數在使用了。所以指標在delete之後,如果不再...
遞迴那些事兒
include include include include 求階乘 int fac int n if n 1 求累加 int add int n 求字串長度 int my strlen const char dest int main 遞迴注意事項 遞迴雖然經典,但是也有他的缺點 第一 遞迴是反...