Linux 下靜態鏈接和動態鏈接的原理及應用
我們知道一個.c 文件經過編譯、鏈接最終可以形成一個可執行文件。
鏈接原理
當我們的程序包含多個文件時,那么這些文件是怎么形成一個目標文件的呢?
這就要涉及到鏈接器。
鏈接器的作用就是將多個目標文件鏈接成一個完整、可加載、可執行的目標文件。其輸入是一組可以重定位的目標文件。
鏈接的主要作用有2個:
符號解析:將目標文件內的引用符號和該符號的定義聯系起來。
將符號定義與存儲器的位置聯系起來,修改對這些符號的引用。
目標文件
典型的目標文件有3種形式
- 可重定位目標文件:這種目標文件后綴通常為.o,這種文件包含已經轉換成機器指令的二進制代碼和數據,但是這種文件還不能直接執行,因為這些指令和數據中往往還引用其他模塊(目標文件)中的符號,這些其他模塊的符號對于本模塊來講還都是未知的,因此這些符號的解析需要鏈接器對這些模塊進行連接,這種操作也稱為“重定位”。
- 可執行目標文件:這種文件同樣包含二進制代碼和數據,區別就是這些文件已經經過鏈接,因此這些文件是可以直接執行的。
- 共享目標文件:這是一種特殊類型的可重定位的目標文件,可以在需要它的程序運行過程或者加載時,動態的加載到內存中運行。這種文件的后綴通常為“.so”, 共享目標文件又稱為“動態庫”文件或者“共享庫”文件。
符號解析
符號解析是鏈接的主要任務之一。只有在正確的解析了符號之后才能更改引用符號的位置,從而完成重定位,生成一個可以被機器直接加載執行的的可執行目標文件。每個可重定位目標文件都有一個符號表。在這個符號表中存儲符號。這些符號分為3類:
本模塊中引用其他模塊所定義的全局符號。
本模塊中定義的全局符號。
本模塊中定義和引用的局部符號。
重定位
在符號解析結束后,每個符號的定義位置及大小都是已知的了。重定位操作只需要將這些符號鏈接起來。在該步驟中,鏈接器需要將所有參與鏈接的目標文件合并,并且為每一個符號分配存儲內容的運行時地址。
重定位分為2個步驟進行:
- 重定位段:該步將所有目標文件中同類型的段合并,生成一個大段。比如,將所有參與鏈接的目標文件的數據段合并,生成一個大的數據段。合并之后,程序中的指令和變量就擁有一個統一并且唯一的運行時地址了。
- 重定位符號引用:由于目標文件中相同的段已經合并,因此程序中對符號的引用位置就都作廢了。這時鏈接器需要修改這些引用符號的地址,使其指向正確的運行時地址。
程序庫
所謂“程序庫”就是包含了一些通用函數的數據和二進制可執行機器碼的文件。這些文件是目標文件的一種,其不能單獨執行。但是若與其他的可執行程序結合起來就可以執行了。
從鏈接方式上區別,程序庫可分為靜態庫和動態庫(共享庫)兩種:
- 靜態庫:是在可執行程序運行前就已經加入到執行代碼中,成為執行程序的一部分來執行的。
- 動態庫:是在執行程序啟動時加載到執行程序中,可以被多個執行程序共享使用。
靜態庫
靜態庫是一些目標代碼的集合。Linux下靜態目標文件一般以.a作為目標文件的后綴。在Linux環境下使用ar命令來創建一個靜態庫。靜態庫的優點就是在生成時已經編譯成可重定位的目標文件,節省了編譯時間,并且在編譯時把代碼復制到可執行代碼段中,這樣可執行程序就可以單獨直接運行,但是缺點也是顯而易見的,就是可執行文件可能會變得很臃腫。
靜態庫的創建
本文以四則運算來創建一個靜態庫,該靜態庫中包含四個函數:加、減、乘、除。
生成一個可重定位的目標文件。
在Linux下使用 ar 命令創建一個靜態庫,或者將目標文件加入到一個已經存在的靜態庫中。其使用方法如下:
ar rcs 靜態庫名 目標文件1 目標文件2 ... 目標文件n
該命令表示將目標文件1~n加入到指定的靜態庫中。若該靜態庫不存在,則創建靜態庫文件,并將庫文件的擴展名命名為.a, 其中rcs這三個參數分別表示:把列表中的目標文件加入到靜態庫中(r);若指定的靜態庫不存在,則創建該庫文件(c);更新靜態庫文件的索引,使之包含新加入的目標文件的內容(s)。
使用生成的 static_lib.o 目標文件創建一個靜態庫 static_lib.a
靜態庫的使用
創建的靜態庫需要鏈接到應用程序中才能使用,為了方便引用,我們創建一個頭文件,使用時把該頭文件包含到應用程序中。
編寫應用程序
編譯
動態庫
動態庫又稱為共享庫或者動態鏈接庫。在 Linux 環境下為 so 文件。動態庫是在程序運行時加載的。當一個應用程序裝載了一個動態庫后,其他應用程序仍可以裝載同一個動態庫。這個被多個進程同時使用的動態庫在內存中只有一個副本,因此動態庫易于程序模塊的更新,更新庫并不影響應用程序使用舊的、非向后兼容的版本。
創建動態庫
我們依然使用以四則運算來創建一個動態庫,該動態庫中包含四個函數:加、減、乘、除。
Linux 下使用 gcc 創建一個動態庫。由于動態庫可以被多個進程共享加載,所以需要生成位置無關的目標文件。因此需要使用 gcc 編譯器的 -fPIC 選項,該選項用于生成位置無關的代碼。除了使用 -fPIC 選項,還需要使用 -shared 選項,該選項將位置無關的代碼制作為動態庫。
創建動態庫的方法如下:
動態庫的使用
為了使應用程序可以正確的引用該庫中的全局符號,需要制作一個包含該動態庫文件中的全局符號聲明的頭文件。
編寫一個應用程序使用動態庫
運行