靜態鏈接與動態鏈接

        首先我們來介紹一下靜態鏈接於動態鏈接的基本思想,所謂的靜態鏈接就是將要鏈接的多個文件中的相似段進行合併,再進行地址的重定位,在程序運行之前就形成一個大的單獨可執行的文件。所謂的動態鏈接就是把程序按照模塊拆分成各個相對獨立的部分,在程序運行時纔將他們鏈接在一起形成一個完整的程序,而不是像靜態鏈接一樣把所有的程序模塊都鏈接成一個單獨可執行的文件。

       在動態鏈接的實現中涉及到一個動態共享對象的問題,這與操作系統的平臺有關。在Linux系統中,ELF動態鏈接文件被稱爲動態共享對象(DSO, Dynamic SharedObjects),簡稱共享對象,它們一般都以“.so”爲擴展名的一些文件;而在Windows系統中,動態鏈接文件被稱爲動態鏈接庫(Dynamical LinkingLibarry),它們通常就是我們平時很常見的以“.dll”爲擴展名的文件。在動態鏈接方式的文件中,動態鏈接就是把鏈接這個過程從本來的程序裝載前被推遲到了裝載的時候才進行。

請參看如下代碼示例:

/*Program1.c */
#include "Lib.h"

int main()
{
    foobar(1) ;
    return 0 ;
}

/*Program2.c */
#include "Lib.h"

int main()
{
    foobar(2) ;
    return 0 ;
}

/*Lib.c*/
#include <cstdio>

void foobar(int i)
{
    printf("Printing from Lib.so %d\n", i) ;
}

/*Lib.h*/
#ifndef LIB_H
#define LIB_H

void foobar(int i) ;

#endif

      這個程序很簡單,兩個程序的主要模塊Program1.c 和Program2.c分別調用了Lib.c裏面的foobar()函數。爲了對Lib.c進行動態鏈接,我們把Lib.c編譯成爲一個共享對象文件(本文的所有操作均在linux下進行):

gcc–fPIC –shared –o Lib.so Lib.c

      經過編譯之後,我們就會得到一個Lib.so文件,這就是包含了Lib.c的foobar()函數的共享對象文件,接下來我們分別編譯Program1.c 和 Program2.c:

gcc–o Program1 Program1.c ./Lib.so

gcc–o Program2 Program2.c ./Lib.so

     Porgram1.c被編譯成Program1.o之後,被鏈接成爲可執行程序Program1.那麼細心的朋友不禁要問,這和靜態鏈接到底會有什麼區別呢?他們的不同點就發生在Program1.o被鏈接成爲可執行文件的這一步。在靜態鏈接方式中,這一步鏈接過程會把Program1.o和Lib.o鏈接到一起,並且最終產生輸出可執行文件Program1。但是在動態鏈接方式中,Lib.o並沒有被鏈接進來,鏈接的輸入目標文件只有Program1.o(當然還包括C語言的運行庫)。但是在編譯的時候使用到了共享對象文件Lib.so呀,這到底是怎麼回事兒呢?它難道不參與鏈接麼?它當然會參與鏈接,只不過這個鏈接的時期被推遲到裝載的過程中。當鏈接器將Program1.o鏈接成可執行文件時,這時候鏈接器必須確定Program1.o中所引用的foobar()函數的性質(在Program1中,在Program1.c被編譯成爲Program.o時,編譯器是不知道函數foobar()的地址的)。如果foobar()是一個定義於其他目標模塊中的函數,那麼鏈接器將會按照靜態鏈接的規則,將Program1.o中的foobar()中的foobar()地址引用重定位;如果foobar()是一個定義在某個動態共享對象中的函數,那麼鏈接器就會將這個符號的引用標記爲一個動態鏈接的符號,不對它進行地址的重定位,把這個過程推遲到裝載的時候再進行。

      那麼現在的問題就是:鏈接器是如何知道foobar()的引用是一個靜態符號還是一個動態符號呢?這就是我們在編譯的時候要帶上參數Lib.so的原因。Lib.so中保存了完整地 符號信息,所以把Lib.so也作爲鏈接的輸入文件之一,鏈接器在解析符號時就可以知道:foobar()是一個定義在Lib.so的動態符號。這樣鏈接器就可以對foobar()的引用做特殊處理,使它成爲一個對動態符號的引用。


參考書目:《程序員的自我修養》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章