1 庫的分類
依據鏈接時期的不同,庫又有靜態庫和動態庫之分。
靜態庫是在鏈接階段被鏈接的。所以生成的可執行文件就不受庫的影響了。即使庫被刪除了,程序依舊能夠成功執行。
有別於靜態庫,動態庫的鏈接是在程序執行的時候被鏈接的。所以,即使程序編譯完,庫仍須保留在系統上,以供程序執行時調用。
2 靜態庫和動態庫的比較
鏈接靜態庫事實上從某種意義上來說也是一種粘貼復制。僅僅只是它操作的對象是目標代碼而不是源代碼而已。由於靜態庫被鏈接后庫就直接嵌入可運行文件里了,這樣就帶來了兩個問題。
首先就是系統空間被浪費了。這是顯而易見的,想象一下,假設多個程序鏈接了同一個庫,則每個生成的可運行文件就都會有一個庫的副本,必定會浪費系統空間。
再者,人非聖賢,即使是精心調試的庫,也難免會有錯。一旦發現了庫中有bug,拯救起來就比較麻煩了。必須一一把鏈接該庫的程序找出來。然后又一次編譯。
而動態庫的出現正彌補了靜態庫的以上弊端。
由於動態庫是在程序執行時被鏈接的,所以磁盤上僅僅須保留一份副本,因此節約了磁盤空間。假設發現了bug或要升級也非常easy,僅僅要用新的庫把原來的替換掉即可了。
那么。是不是靜態庫就一無是處了呢?
答曰:非也非也。不是有句話么:存在即是合理。
靜態庫既然沒有湮沒在滔滔的歷史長河中,就必定有它的用武之地。想象一下這種情況:假設你用libpcap庫編了一個程序,要給被人執行,而他的系統上沒有裝pcap庫。該怎么解決呢?最簡單的辦法就是編譯該程序時把全部要鏈接的庫都鏈接它們的靜態庫。這樣。就能夠在別人的系統上直接執行該程序了。
所謂有得必有失。正由於動態庫在程序執行時被鏈接。故程序的執行速度和鏈接靜態庫的版本號相比必定會打折扣。
然而瑕不掩瑜,動態庫的不足相對於它帶來的優點在現今硬件下簡直是微不足道的,所以鏈接程序在鏈接時通常是優先鏈接動態庫的。除非用-static參數指定鏈接靜態庫。
3 動態鏈接庫
隱式調用
1. 創建動態鏈接庫
#include<stdio.h> void hello() { printf("hello world/n"); }
用命令gcc -fPIC -shared hello.c -o libhello.so編譯為動態庫。
能夠看到。當前文件夾下多了一個文件libhello.so。
gcc參數
-shared:
該選項指定生成動態連接庫(讓連接器生成T類型的導出符號表,有時候也生成弱連接W類型的導出符號)。不用該標志外部程序無法連接。
相當於一個可運行文件
-fpic:
表示編譯為位置獨立的代碼。不用此選項的話編譯后的代碼是位置相關的所以動態加載時是通過代碼拷貝的方式來滿足不同進程的須要,而不能達到真正代碼段共享的目的。
2. 再編輯一個測試文件test.c,內容例如以下
編譯 gcc test.c -lhello
-l 選項告訴編譯器要使用hello這個庫。奇怪的地方是動態庫的名字是libhello.so,這里卻使用hello.
但這樣還不行。編譯會出錯。
In function `main':
test.c:(.text+0x1d): undefined reference to `hello'
collect2: ld returned 1 exit status
這是由於hello這個庫在我們自己的路徑中,編譯器找不到。
須要使用-L選項,告訴hello庫的位置
gcc test.c -lhello -L. -o test
-L .告訴編譯器在當前文件夾中查找庫文件
3. 編譯成功后運行./test, 仍然出錯
說找不到庫
有兩種方法:
一、能夠把當前路徑增加 /etc/ld.so.conf中然后執行ldconfig。或者以當前路徑為參數執行ldconfig(要有root權限才行)。
二、把當前路徑增加環境變量LD_LIBRARY_PATH中
當然。假設你認為不會引起混亂的話,能夠直接把該庫拷入/lib,/usr/lib/等位置(無可避免,這樣做也要有權限),這樣鏈接器和載入器就都能夠准確的找到該庫了。
我們採用另外一種方法:
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
這樣,再運行就成功了。
注:
假如如今須要在已有的環境變量上加入新的路徑名,則採用例如以下方式:
顯式調用
顯式調用須要包括頭文件#include <dlfcn.h>。
涉及到以下幾個函數:dlopen()、dlsym()、dlerror()、dlclose()。
dlopen()函數以指定模式打開指定的動態鏈接庫文件,並返回一個句柄給dlsym()的調用進程。
使用dlclose()來卸載打開的庫。當動態鏈接庫操作函數運行失敗時,dlerror能夠返回出錯信息,返回值為NULL時表示操作函數運行成功。
編譯時候要增加 -ldl (指定dl庫)
詳細的函數原型例如以下:
void *dlopen(const char *filename, int flag); char *dlerror(void); void *dlsym(void *handle, const char *symbol); int dlclose(void *handle);
dlopen以指定模式打開指定的動態連接庫文件。並返回一個句柄給調用進程,dlerror返回出現的錯誤,dlsym通過句柄和連接符名稱獲取函數名或者變量名,dlclose來卸載打開的庫。
如果已經生成libcaculate.so庫,里面定義了add(),sub(),mul(),div()等函數。這里給出調用演示樣例:
#include <stdio.h> #include <stdlib.h> #include <dlfcn.h> //動態鏈接庫路徑 #define LIB_CACULATE_PATH "./libcaculate.so" //函數指針 typedef int (*CAC_FUNC)(int, int); int main() { void *handle; char *error; CAC_FUNC cac_func = NULL; //打開動態鏈接庫 handle = dlopen(LIB_CACULATE_PATH, RTLD_LAZY); if (!handle) { fprintf(stderr, "%s\n", dlerror()); exit(EXIT_FAILURE); } //清除之前存在的錯誤 dlerror(); //獲取一個函數 *(void **) (&cac_func) = dlsym(handle, "add"); if ((error = dlerror()) != NULL) { fprintf(stderr, "%s\n", error); exit(EXIT_FAILURE); } printf("add: %d\n", (*cac_func)(2,7)); cac_func = (CAC_FUNC)dlsym(handle, "sub"); printf("sub: %d\n", cac_func(9,2)); cac_func = (CAC_FUNC)dlsym(handle, "mul"); printf("mul: %d\n", cac_func(3,2)); cac_func = (CAC_FUNC)dlsym(handle, "div"); printf("div: %d\n", cac_func(8,2)); //關閉動態鏈接庫 dlclose(handle); exit(EXIT_SUCCESS); }
4 靜態鏈接庫
仍使用剛才的hello.c和test.c。
1. gcc -c hello.c 注意這里沒有使用-shared選項
2. 把目標文件歸檔 ar -r libhello.a hello.o
程序 ar 配合參數 -r 創建一個新庫 libhello.a 並將命令行中列出的對象文件插入。採用這樣的方法,假設庫不存在的話,參數 -r 將創建一個新的庫。而假設庫存在的話,將用新的模塊替換原來的模塊。
3. 在程序中鏈接靜態庫
gcc test.c -lhello -L. -static -o hello.static
或者 gcc test.c libhello.a -L. -o hello.static
生成的hello.static就不再依賴libhello.a了LD_LIBRARY_PATH