1. 程式人生 > >深入淺出靜態連結和動態連結

深入淺出靜態連結和動態連結

        作為一名C/C++程式設計師,對於編譯連結的過程要了然於胸。首先大概介紹一下,編譯分為3步,首先對原始檔進行預處理,這個過程主要是處理一些#號定義的命令或語句(如巨集、#include、預編譯指令#ifdef等),生成*.i檔案;然後進行編譯,這個過程主要是進行詞法分析、語法分析和語義分析等,生成*.s的彙編檔案;最後進行彙編,這個過程比較簡單,就是將對應的彙編指令翻譯成機器指令,生成可重定位的二進位制目標檔案。以上就是編譯的過程,下面主要介紹兩種連結方式--靜態連結和動態連結。

        靜態連結和動態連結兩者最大的區別就在於連結的時機不一樣,靜態連結是在形成可執行程式前,而動態連結的進行則是在程式執行時,下面來詳細介紹這兩種連結方式。

一、靜態連結

1.為什麼要進行靜態連結

        在我們的實際開發中,不可能將所有程式碼放在一個原始檔中,所以會出現多個原始檔,而且多個原始檔之間不是獨立的,而會存在多種依賴關係,如一個原始檔可能要呼叫另一個原始檔中定義的函式,但是每個原始檔都是獨立編譯的,即每個*.c檔案會形成一個*.o檔案,為了滿足前面說的依賴關係,則需要將這些原始檔產生的目標檔案進行連結,從而形成一個可以執行的程式。這個連結的過程就是靜態連結

2.靜態連結的原理

         由很多目標檔案進行連結形成的是靜態庫,反之靜態庫也可以簡單地看成是一組目標檔案的集合,即很多目標檔案經過壓縮打包後形成的一個檔案,如下圖,使用ar命令的-a引數檢視靜態庫的組成:


        這裡的*.o目標檔案在我前面的部落格《從編寫原始碼到程式在記憶體中執行的全過程解析》中已經講的很清楚了,不清楚的可以看一下。

        以下面這個圖來簡單說明一下從靜態連結到可執行檔案的過程,根據在原始檔中包含的標頭檔案和程式中使用到的庫函式,如stdio.h中定義的printf()函式,在libc.a中找到目標檔案printf.o(這裡暫且不考慮printf()函式的依賴關係),然後將這個目標檔案和我們hello.o這個檔案進行連結形成我們的可執行檔案。


        這裡有一個小問題,就是從上面的圖中可以看到靜態執行庫裡面的一個目標檔案只包含一個函式,如libc.a裡面的printf.o只有printf()函式,strlen.o裡面只有strlen()函式。


        我們知道,連結器在連結靜態連結庫的時候是以目標檔案為單位的。比如我們引用了靜態庫中的printf()函式,那麼連結器就會把庫中包含printf()函式的那個目標檔案連結進來,如果很多函式都放在一個目標檔案中,很可能很多沒用的函式都被一起連結進了輸出結果中。由於執行庫有成百上千個函式,數量非常龐大,每個函式獨立地放在一個目標檔案中可以儘量減少空間的浪費,那些沒有被用到的目標檔案就不要連結到最終的輸出檔案中。

3.靜態連結的優缺點

        靜態連結的缺點很明顯,一是浪費空間,因為每個可執行程式中對所有需要的目標檔案都要有一份副本,所以如果多個程式對同一個目標檔案都有依賴,如多個程式中都呼叫了printf()函式,則這多個程式中都含有printf.o,所以同一個目標檔案都在記憶體存在多個副本;另一方面就是更新比較困難,因為每當庫函式的程式碼修改了,這個時候就需要重新進行編譯連結形成可執行程式。但是靜態連結的優點就是,在可執行程式中已經具備了所有執行程式所需要的任何東西,在執行的時候執行速度快。

問題:

二、動態連結
1.為什麼會出現動態連結

        動態連結出現的原因就是為了解決靜態連結中提到的兩個問題,一方面是空間浪費,另外一方面是更新困難。下面介紹一下如何解決這兩個問題。

2.動態連結的原理

        動態連結的基本思想是把程式按照模組拆分成各個相對獨立部分,在程式執行時才將它們連結在一起形成一個完整的程式,而不是像靜態連結一樣把所有程式模組都連結成一個單獨的可執行檔案。下面簡單介紹動態連結的過程:

        假設現在有兩個程式program1.o和program2.o,這兩者共用同一個庫lib.o,假設首先執行程式program1,系統首先載入program1.o,當系統發現program1.o中用到了lib.o,即program1.o依賴於lib.o,那麼系統接著載入lib.o,如果program1.o和lib.o還依賴於其他目標檔案,則依次全部載入到記憶體中。當program2執行時,同樣的載入program2.o,然後發現program2.o依賴於lib.o,但是此時lib.o已經存在於記憶體中,這個時候就不再進行重新載入,而是將記憶體中已經存在的lib.o對映到program2的虛擬地址空間中,從而進行連結(這個連結過程和靜態連結類似)形成可執行程式。

3.動態連結的優缺點

        動態連結的優點顯而易見,就是即使需要每個程式都依賴同一個庫,但是該庫不會像靜態連結那樣在記憶體中存在多分,副本,而是這多個程式在執行時共享同一份副本;另一個優點是,更新也比較方便,更新時只需要替換原來的目標檔案,而無需將所有的程式再重新連結一遍。當程式下一次執行時,新版本的目標檔案會被自動載入到記憶體並且連結起來,程式就完成了升級的目標。但是動態連結也是有缺點的,因為把連結推遲到了程式執行時,所以每次執行程式都需要進行連結,所以效能會有一定損失。

        據估算,動態連結和靜態連結相比,效能損失大約在5%以下。經過實踐證明,這點效能損失用來換區程式在空間上的節省和程式構建和升級時的靈活性是值得的。

4.動態連結地址是如何重定位的呢?

        前面我們講過靜態連結時地址的重定位,那我們現在就在想動態連結的地址又是如何重定位的呢?雖然動態連結把連結過程推遲到了程式執行時,但是在形成可執行檔案時(注意形成可執行檔案和執行程式是兩個概念),還是需要用到動態連結庫。比如我們在形成可執行程式時,發現引用了一個外部的函式,此時會檢查動態連結庫,發現這個函式名是一個動態連結符號,此時可執行程式就不對這個符號進行重定位,而把這個過程留到裝載時再進行。