庫檔案的概念
在很多情況下,原始碼檔案也可以被多個程式共用。因此要降低工作量的第一步就是將這些原始碼檔案只編譯一次,然後在需要的時候將它們連結進不同的可執行檔中。雖然這項技術能夠節省編譯時間,但其缺點是在連結的時候仍然需要為所有目標檔案命名。此外,大量的目標檔案會散落在系統上的各個目錄中,從而造成目錄中內容的混亂。
為解決這個問題,可以將一組目標檔案組織成一個被稱為物件程式庫的單元。物件程式庫分為兩種:靜態和共用的。共用庫是一種更加現代化的物件程式庫,它比靜態庫更具優勢。
靜態庫也被稱為歸檔檔案,它是Unix/linux系統提供的一種庫。
(1)靜態庫的特徵
1)靜態庫在使用時,直接把代碼複製到目標檔案中。
2)優點:不需要跳轉,效率比較好,脫離靜態庫檔案。
3)缺點:目標檔案會比較大,修改和維護都不太方便。
(2)共用庫的特徵
1)共用庫在使用時,直接把點嗎所對應的地址複製過來。
2)優點:目標檔案比較小,修改和維護都方便。
3)缺點:需要跳轉,效率比較低,不能脫離共用庫檔案。
(3)基本命令
ldda.out => 表示查看a.out所連結的共用庫資訊
gcc/cc-static xxx.c => 表示以靜態庫的方式進行處理
比較發現,靜態庫方式產生的檔案比較大。
靜態庫的產生和使用步驟
(1)靜態庫的產生步驟
1)編寫原始碼(xxx.c檔案)
vi add.c檔案
2)只編譯不連結產生目標檔案(xxx.o完呢減)
gcc/cc -c add.c
3)產生靜態庫檔案
ar -r/*插入*/ lib庫名.a 目標檔案
ar-r libadd.a add.o
注意:
靜態庫檔案名稱的名字規則:以lib開頭,以.a結尾
靜態庫檔案名稱和庫名是不同的概念,庫名沒有首碼和尾碼
(2)靜態庫的使用步驟
1)編寫測試原始碼(xxx.c)
vi main.c檔案
2)只編譯不連結產生目標檔案(xxx.o)
gcc -c main.c
3)連結測試檔案和靜態庫檔案,連結的方式有三種:
a.直接連結
gcc main.o libadd.a
b.使用編譯選項進行連結
gcc main.o -l 庫名 -L 庫檔案所在的路徑
gcc main.o -l add -L .
c.配置環境變數LIBRARY_PATH
export LIBRARY_PATH=$LIBRARY_PATH:.
gcc main.o -l add
共用庫的產生和使用步驟
(1)共用庫的產生步驟
1)編寫原始碼(xxx.c)
vi add.c檔案
2)只編譯不連結產生目標檔案(xxx.o)
gcc -c -fpic/*小模式,代碼少*/ add.c
3)產生共用庫檔案
gcc -shared 目標檔案(xxx.o) -o lib庫名.so
gcc -shared/*共用*/ add.o -o libadd.so
(2)共用庫的使用步驟
1)編寫測試原始碼(xxx.c)
vi main.c檔案
2)只編譯不連結產生目標檔案(xxx.o)
gcc/cc -c main.c
3)連結測試檔案和共用庫檔案,連結的方式有三種:
a.直接連結
gcc main.o libadd.so
b.使用編譯選項進行連結
gcc main.o -l 庫名 -L 庫檔案所在的路徑
gcc main.o -l add -L .
c.配置環境變數LIBRARY_PATH
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
gcc main.o -l add
注意:
共用庫的使用要求配置環境變數LD_LIBRARY_PATH的值,主要解決運行時找不到共用庫的問題。
共用庫的動態載入
(1)dlopen函數
#include<dlfcn.h>
void *dlopen(const char *filename, int flag);
第一個參數:字元形式的共用庫函數名
第二個參數:標誌
RTLD_LAZY- 消極式載入
RTLD_NOW - 立即載入
返 回 值:通用類指標,成功返回控制代碼,暫時理解為首地址,失敗返回NULL
函數功能 :
主要用於開啟和載入動態庫
(2)dlerror函數
char *dlerror(void);
函數功能:
主要用於擷取dlopen等函數調用過程發生的最近一個錯誤的詳細資料
(3)dlsym函數
void* dlsym(void *handle, const char *symbol);
第一個參數:控制代碼,也就是dlopen函數的傳回值
第二個參數:字串形式的符號,表示函數名
傳回值:成功返回函數在記憶體中的地址,失敗返回NULL
函數功能:
主要用於根據控制代碼和函數名擷取在記憶體中對應的地址
(4)dlclose函數
int dlclose(void *handle);
函數功能:
主要用於關閉參數handle指定的共用庫,成功返回0,失敗返回非0,當共用庫不再被任何程式使用時,則回收共用庫所佔用的記憶體空間。
注意:
編譯連結時需要增加選項: -ldl
#include <stdio.h>#include <stdlib.h>#include <dlfcn.h>int main(void){//開啟一個動態庫void *handle = dlopen("./dynamic_add/libadd.so", RTLD_LAZY);//判讀開啟是否成功,傳回值為NULL表示開啟失敗if (NULL == handle){//列印失敗原因printf("%s\n", dlerror());exit(-1);}printf("開啟共用庫成功。\n");//讀取動態庫中的函數地址(函數指標的類型必須和需要讀取的函數一致)int (*p_func)(int, int) = dlsym(handle, "add");//判斷讀取是否成功,傳回值為NULL表示讀取失敗if (NULL == p_func){printf("%s\n", dlerror());exit(-1);}//調用動態庫的函數printf("%d\n", p_func(2, 8));//最後關閉動態庫,傳回值非0表示關閉失敗if (0 != dlclose(handle)){printf("%s\n", dlerror());exit(-1);}printf("關閉共用庫成功。\n");return 0;}
總結:
由於與靜態庫相比,共用庫存在很多優勢,因此再當代Unix/linux系統上共用庫用得很多。共用庫的又是主要源自這麼一個事實,即當一個程式與庫連結時,程式所需的目標模組的副本不會被包含進結果可執行檔中。相反,(靜態)連結器將會在可執行檔中添加與程式在運行時所需的共用庫相關的資訊。當檔案執行時,動態連結器會使用這些資訊來載入所需的共用庫。在運行時,所有使用同一共用庫的程式共用該庫在記憶體中的副本。由於共用庫不會被複製到可執行檔中,並且在運行時所有程式都使用共用庫在記憶體中的單個副本,因此共用庫能夠降低系統所需的磁碟空間和記憶體。