鏈結器之靜態鏈結與動態鏈結

2021-07-31 01:40:48 字數 3581 閱讀 8825

一、前言

二、鏈結

在《深入理解計算機系統》一書中,p448,鏈結的定義是:將各種**和資料部分收集起來並組合為乙個單一檔案的過程,這個檔案可被載入(或被拷貝)到儲存器並執行。這是定義,那麼實際中呢?鏈結的過程就是將.cpp對應的.obj(windows下)和.lib(其內部也是一組.obj,即一組可重定位目標檔案)合併,生成乙個可執行目標檔案。這裡首先說明,鏈結分為靜態鏈結和動態鏈結,靜態鏈結是由靜態鏈結器完成,而動態鏈結是由動態鏈結器完成。

1、靜態鏈結

靜態鏈結分為符號解析和重定位兩個步驟,其中符號解析主要是將乙個模組內的符號引用與該模組的符號表中的乙個符號定義關聯,這裡的模組就是單個可重定位目標檔案,在windows裡面是.obj檔案,而重定位將多個模組的符號表中的每乙個符號定義以記憶體位址唯一關聯,經過這兩個步驟,就完成了符號引用到符號定義,再到記憶體的完整對映,因為引用乙個符號,不就是為了獲取或改變他在記憶體中的值嘛。

(1)符號解析

在乙個可重定位檔案(模組)中,有三種鏈結器符號:能被其他模組引用全域性符號,引用其他模組的全域性符號,以及本地符號。注意,這裡的本地符號是在當前模組中所有函式定位外面的全域性符號,並且帶有static屬性,並不是函式內部區域性變數,鏈結器不關心函式內部的區域性變數,這一部分由執行時棧管理,如下:

/*********************m.cpp*****************************/

extern int e;

int a;

static int b;

void fun(int c)

int d = c;

return d;

上面所示模組m中,符號a是能被其他模組引用的全域性符號,符號e為引用其他模組的全域性符號,而符號b則是目標m的本地符號,它只能在模組m內部引用,其他模組不可見,另外符號c、d都是模組m的本地過程變數,由執行時棧管理。

由於鏈結器上下文中有不同的符號型別(上述3種),因此鏈結器的符號解析物件大致分為兩種情況:引用本地符號和引用全域性符號。

a.引用本地符號。這個過程非常簡單,直接對照關聯就行

b.引用全域性符號。這個過程就有點複雜了,試想,在兩個模組中同時兩個名字相同的全域性符號,鏈結器該怎麼處理呢?當然是定義一套自己的規則去避開一些麻煩,這些規則是建立在強符號和弱符號定義的基礎之上的,強符號是指函式和已經定義的全域性變數,弱符號是指未定義的全域性變數,這一套規則就是:如果有多個強符號,就報錯;如果有乙個強符號和多個弱符號,就選擇強符號;如果有多個弱符號,就隨機選擇乙個弱符號。如下,第一種情況示例會報錯

/fun1.cpp*/

int main()

/fun2.cpp*/

int main()

第二種情況例項不會報錯,但結果與你的預期有差距

/m1.cpp*/

int x=123;

void fun();

int main()

/fun.cpp*/

int x;

void fun()

如果執行程式,則會輸出x=234,因為在模組fun.cpp中鏈結的是定義在m模組中的強符號x。

(2)重定位

只需要理解一點,在鏈結過程中,當所有符號引用與符號定位關聯之後,我們仍然無法知道引用該符號所得到的內容,因為內容是儲存在記憶體中的,這就需要重定位來完成,重定位包括重定位節和符號定義、重定位節中的符號引用兩個步驟,完成重定位之後,就知道了該引用所引用的記憶體值。

(3)與靜態庫鏈結

靜態庫就是一開始提到的.lib檔案,它是乙個目標模組的集合,其包含一組目標模組(可重定位目標檔案),當我們將自己的cpp檔案與lib庫檔案鏈結生成可執行檔案時,只是將靜態庫中被應用程式引用的模組拷貝出來,其符號解析過程還是和上述一樣。比如我們自己編寫了乙個m.cpp檔案,需要用到別人提供給的.lib中的一些函式,比如fun1.cpp,fun2.cpp,在.lib庫檔案中,就應該是有fun1、和fun2模組對應的可重定位模組,假設名為fun1.obj、fun2.obj,而我們自己編寫的m模組被編譯器和彙編器生成為m.obj的目標檔案,鏈結過程就是合併m.obj、fun1.obj、fun2.obj的過程,即它並不是拷貝整個.lib檔案,只是拷貝了fun1和fun2兩個檔案,這也是為了減少儲存器資源消耗的方法,那麼鏈結器是如何實現這一過程的呢?

它通過3個集合:e、u、d來完成。其中集合e表示鏈結器維護的乙個可重定位目標檔案的集合,這個集合剛開始為空,鏈結完成之後,鏈結器就合併這個集合中的目標檔案,生成可執行目標檔案;集合u表示為解析的符號引用,即在我們編寫的.cpp檔案中引用了但尚未定義的符號,剛開始也為空;集合d表示已定義的符號集合,初始時也為空。具體過程可參考《深入了解計算機系統》(原書第二版)p460內容。

總之,與靜態庫鏈結(即引用靜態庫)需要明白兩個概念:(1)靜態庫就是一組可重定位目標檔案集合(在windows下是.obj檔案);(2)鏈結過程中,應用程式只拷貝在自己源程式中引用的目標檔案,並不是拷貝整個.lib庫檔案。

(4)動態鏈結共享庫

靜態庫有自己的優點,也有缺點,缺點就是需要定期維護更新,然後引用該靜態庫檔案的應用程式也要重新與該庫檔案鏈結才能正確執行,最主要的問題是與靜態庫鏈結需要直接拷貝庫檔案中的目標模組,儘管是選擇性拷貝,但是對於乙個小的程式來說,拷貝一些.lib檔案還是會造成一些記憶體資源浪費。為了解決這些問題,聰明的人就創造了動態鏈結共享庫,動態庫在windows中是用.dll副檔名的檔案(好,現在就到了解釋前言中提高的問題了),記住,動態庫鏈結是由動態鏈結器完成,但是他也需要靜態鏈結器(為什麼呢?看後面解釋)。

這裡要說明,繼續前言的敘述,乙個動態鏈結庫包含.h、.lib、.dll檔案,而真正的可重定位目標模組是放在.dll中的。咦?剛剛講到靜態鏈結庫的時候,可重定位目標模組是放在.lib中,這裡在動態庫中既然是放在.dll裡面的,那麼為什麼還需要乙個.lib檔案呢?這個.lib檔案裡面到底放的是什麼呢?這就是動態鏈結器的設計需要。

動態鏈結器的鏈結過程主要分為兩步:生成部分鏈結的可執行目標檔案、生成完全鏈結的可執行檔案,這兩個步驟分別有靜態鏈結器和動態鏈結器完成(這個過程可參考《深入了解計算機系統》p468圖7-15)。

這裡生成部分鏈結的可執行目標檔案有靜態鏈結器完成,需要使用.lib檔案。這裡.lib檔案中並不包含任何關於動態庫檔案的**和資料內容,只是包含一些重定位和符號表資訊,這使得執行時可以解析對.dll庫檔案中的**和資料的引用。所以這就解釋了我在前言中說明的在鏈結了.lib檔案之後,程式編譯時沒有問題的。

完成部分鏈結之後,就已經生成了乙個可執行目標檔案,在windows中是.exe檔案,但是這只是乙個部分鏈結的可執行目標檔案,它沒有包含任何與庫有關的資料和**內容,直接執行當然會報錯,而報錯資訊正是缺少相應的.dll檔案,這裡,只要把相應的.dll檔案與生成的部分鏈結的可執行目標檔案放在同乙個目錄下,程式就可以執行了,完美。咦?整個動態鏈結的過程還是沒有引用或者拷貝庫檔案(.dll)中的**和資料內容啊,程式怎麼又能執行了呢?那是因為你在執行.exe檔案的時候,程式載入器動態的載入了.dll動態庫檔案,然後與.exe檔案一起執行的,這是載入器幫你完成的,所以你看不到咯。

靜態鏈結與動態鏈結

基於gmp開發第三方庫,後者以動態鏈結庫 靜態庫?對方式發布,為了效率gmp建議對該庫採用靜態鏈結,所以,目的是生成靜態鏈結gmp庫的庫檔案 動態庫?靜態庫?一 生成可執行檔案 1.動態鏈結庫的編譯 mac和linux都是 gcc o param convention1 param conventi...

動態鏈結 靜態鏈結

在linux系統中,ld鏈結器將彙編器編譯出來的目標檔案和靜態庫里的.a檔案鏈結生成可執行檔案。靜態庫中的.a檔案的 會在靜態鏈結過程中新增到可執行檔案中,可執行檔案會變得很大。與靜態鏈結不同,linux系統的ld鏈結器會將動態庫.so檔案進行符號重定位生成可執行檔案,動態庫.so檔案並不新增到可執...

靜態鏈結 動態鏈結

如果函式庫的乙份拷貝是可執行檔案的物理組成部分,那麼我們稱之為靜態鏈結。如果可執行檔案只是包含了檔名,讓載入器在執行時能夠尋找程式所需的函式庫,那麼稱為動態鏈結。即根據函式庫是不是可執行檔案的組成部分區分靜態鏈結和動態鏈結。1 可執行檔案的體積小。2 雖然執行速度稍慢,但是能更加有效的利用磁碟空間,...