程式設計師的自我修養四靜態鏈結

2021-08-07 07:37:54 字數 2719 閱讀 4816

真正了不起的程式設計師對自己的程式的每乙個位元組都了如執掌

/*

a.c*/

extern

intshared;

int main()//

全域性符號

/*b.c

*/int shared=1;//

全域性符號

void swap(int* a,int* b)//

全域性符號

對於鏈結器來說,整個鏈結過程中,它就是將幾個輸入目標檔案加工後合併成乙個輸出檔案。

但鏈結器如何將各個段合併到輸出檔案?

乙個簡單方案就是將輸入目標檔案按照次序疊加起來,就是直接將各個目標檔案依次合併。但這樣有乙個問題,在有很多輸入檔案的情況下,輸出檔案將會有很多零散的段,這樣就是早成記憶體空間大量的內部碎片,造成空間浪費。

乙個更加實際的方法是將相同性質的段合併到一起。

在第一步掃瞄和空間分配階段,鏈結器已經對空間進行了分配,這時候,輸入我呢見中的各個段在鏈結後的虛擬位址就已經確定了。這一步完成後,鏈結器開始計算各個符號的虛擬位址,因為各個符號在段內的相對位置是固定的,這個時候符號位址也已經確定了。只不過聯結器要給每個符號加上乙個偏移量,使它們能夠調整到正確的虛擬位址。

在完成空間和位址的分配步驟之後,鏈結器就進入了符號解析與重定位的步驟。

這個時候,編譯器並不知道」shared」和」swap」的位址,它們是定義在其他目標檔案中的,現在只是乙個臨時假位址。

elf檔案中,有乙個叫做重定位表的結構專門用來儲存這些與重定位相關的資訊,它在elf檔案中往往是乙個或多個段。

重定位表有時候也叫做重定位段。

重定位表的結構,它是乙個elf32——rel結構的陣列:

typedef struct

elf32_rel;

在重定位的過程中也伴隨這符號解析的過程,每個重定位的入口都是對乙個符號的引用,那麼當鏈結器須要對某個符號的引用進行重定位時,它就要確定這個符號的目標位址。這時候鏈結器就會去查詢由所有輸入目標檔案的符號組成的全域性符號表,找到相應的符號進行重定位。

鏈結器掃面完所有輸入目標檔案之後,所有這些未定義的符號都應該能夠在全域性符號表中找到,否則鏈結器就報符號未定義錯誤。

common塊機制:當不同的目標檔案需要的common塊空間大小不一致,以最大的那塊為準。

common型別的列檢規則是針對弱符號的情況。

未初始化的全域性變數標記為乙個common型別的變數。

在編譯c++的時候,當模板在乙個編譯單元裡被例項化時,它並不知道自己是否在別的編譯單元也被例項化,所以,當乙個模板在多個編譯單元同時例項化成相同的型別的時候,必然會產生重複**。

這裡最簡單的方法就是將這些**都儲存下來,這裡會產生幾個問題:

乙個比較好的做法就是將每個模板的例項**都單獨存放在乙個段裡,當同樣的模板生成同樣的名字,這樣鏈結器在最終鏈結的時候可以區分這些相同的模板例項段,然後將它們合併入最後的**段。

gcc把這種類似的做法叫做」link once」,它的做法是將這種型別的段命名為」.gnu.linkonce.name」,其中」name」是該模板函式例項的修飾後名稱

這種消除重複**的方法對於外部內聯函式和虛函式一樣類似。

函式級別鏈結

讓所有函式都像前面模板函式一樣,單獨儲存到乙個段裡面。當鏈結器須要用到某個函式的時候,它就將它合併到輸出檔案種,對於那些沒有用的函式則將它們拋棄。

c++全域性物件的建構函式在main之前被執行,c++全域性物件的析構函式在main之後被執行。

linum系統下一般程式的入口是」_start」,當我們程式與glibc鏈結在一起形成最終可執行檔案之後,這個函式就是程式的初始化入口,它完成之後,就會呼叫main函式,執行程式主體。mian函式執行完成後,返回初始化部分進行一些清理工作,然後結束程序。

下面的兩種特殊段,就是在main前後執行:

api與abi

它們都是應用程式介面,只是它們描述的結構所在的層面不一樣。

api往往指源**級別的介面,而abi是指二進位制層面的介面,abi相容程度比api更加嚴格。

程式之所以有用,因為它會有輸入輸出,乙個程式要輸入輸出最簡單的辦法就是使用作業系統提供的應用程式程式設計介面(api)

那麼程式是如何使用作業系統提供的api。在一般情況下,一種語言的開發環境就會附帶語言庫,這些庫是對作業系統api的包裝。

乙個靜態檔案也可以簡單的看成一組目標檔案的集合,即很多目標檔案經過壓縮打包後形成的乙個檔案。

鏈結器在鏈結靜態庫的時候,是以目標檔案為單位的,所以乙個目標檔案中只包含乙個函式。

絕大部分情況下,我們使用鏈結器提供的預設鏈結規則對目標檔案進行鏈結,但有時候也有特殊情況。

如果把整個鏈結過程比作一台計算機,那麼id鏈結器就是計算機的cpu,所有的目標檔案、庫檔案就是輸入,鏈結結果輸出的可執行檔案就是輸出。

鏈結控制指令碼」程式」使用一種特殊的語言寫成,即id的鏈結指令碼語言。

我們把輸入檔案的段稱為輸入段,輸出檔案中的段稱為輸出端

《程式設計師的自我修養》筆記 靜態鏈結

在通過編譯和彙編後,就生成了目標檔案,鏈結就是把這些目標檔案加工後合併成乙個輸出檔案的過程。鏈結過程可以分為兩步 第一步 空間與位址分配。掃瞄所有的輸入目標檔案,獲得它們每個各個段的長度 屬性和位置,並且將輸入目標檔案中的符號表中所有 的符號定義和符號引用收集起來,統一放到乙個全域性符號表。這一步中...

程式設計師的自我修養 編譯和鏈結

hello world程式hello.c include int main 在linux下,使用gcc編譯和執行hello world程式時,只需使用最簡單的命令。gcc hello hello.c hello hello world 事實上,上述過程可以分解為4個步驟,分別是預處理 prepres...

程式設計師的自我修養(十)動態鏈結

動態鏈結其實就是把鏈結的過程推遲到了執行時再進行。特點 重定位位址無關 fpic與 fpie 模組內部的函式呼叫 跳轉等 這種不需要重定位 模組內部的資料訪問,比如模組中定義的全域性變數 靜態變數 相對定址 模組外部的函式呼叫 跳轉等 got,但是儲存的是目標函式的位址 模組外部的資料訪問,比如其他...