linux下有兩種庫(kù):動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)(共享庫(kù))
二者的不同點(diǎn)在于代碼被載入的時(shí)刻不同。
靜態(tài)庫(kù)的代碼在編譯過(guò)程中已經(jīng)被載入可執(zhí)行程序,因此體積比較大。
動(dòng)態(tài)庫(kù)(共享庫(kù))的代碼在可執(zhí)行程序運(yùn)行時(shí)才載入內(nèi)存,在編譯過(guò)程中僅簡(jiǎn)單的引用,因此代碼體積比較小。
不同的應(yīng)用程序如果調(diào)用相同的庫(kù),那么在內(nèi)存中只需要有一份該動(dòng)態(tài)庫(kù)(共享庫(kù))的實(shí)例。
靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)的最大區(qū)別,靜態(tài)情況下,把庫(kù)直接加載到程序中,而動(dòng)態(tài)庫(kù)鏈接的時(shí)候,它只是保留接口,將動(dòng)態(tài)庫(kù)與程序代碼獨(dú)立,這樣就可以提高代碼的可復(fù)用度,和降低程序的耦合度。
靜態(tài)庫(kù)在程序編譯時(shí)會(huì)被連接到目標(biāo)代碼中,程序運(yùn)行時(shí)將不再需要該靜態(tài)庫(kù)。
動(dòng)態(tài)庫(kù)在程序編譯時(shí)并不會(huì)被連接到目標(biāo)代碼中,而是在程序運(yùn)行是才被載入,因此在程序運(yùn)行時(shí)還需要?jiǎng)討B(tài)庫(kù)存在
一 靜態(tài)庫(kù)
這類(lèi)庫(kù)的名字一般是libxxx.a;利用靜態(tài)函數(shù)庫(kù)編譯成的文件比較大,因?yàn)檎麄€(gè) 函數(shù)庫(kù)的所有數(shù)據(jù)都會(huì)被整合進(jìn)目標(biāo)代碼中,他的優(yōu)點(diǎn)就顯而易見(jiàn)了,即編譯后的執(zhí)行程序不需要外部的函數(shù)庫(kù)支持,因?yàn)樗惺褂玫暮瘮?shù)都已經(jīng)被編譯進(jìn)去了。當(dāng)然這也會(huì)成為他的缺點(diǎn),因?yàn)槿绻o態(tài)函數(shù)庫(kù)改變了,那么你的程序必須重新編譯。
靜態(tài)庫(kù)的代碼在編譯時(shí)鏈接到應(yīng)用程序中,因此編譯時(shí)庫(kù)文件必須存在,并且需要通過(guò)“-L”參數(shù)傳遞路徑給編譯器,應(yīng)用程序在開(kāi)始執(zhí)行時(shí),庫(kù)函數(shù)代碼將隨程序一起調(diào)入進(jìn)程內(nèi)存段直到進(jìn)程結(jié)束,其執(zhí)行過(guò)程不需要原靜態(tài)庫(kù)存在。
在UNIX中,使用ar命令創(chuàng)建或者操作靜態(tài)庫(kù)
ar archivefile objfile
archivefile:archivefile是靜態(tài)庫(kù)的名稱(chēng)
objfile:objfile是已.o為擴(kuò)展名的中間目標(biāo)文件名,可以多個(gè)并列
參數(shù) 意義
-r 將objfile文件插入靜態(tài)庫(kù)尾或者替換靜態(tài)庫(kù)中同名文件
-x 從靜態(tài)庫(kù)文件中抽取文件objfile
-t 打印靜態(tài)庫(kù)的成員文件列表
-d 從靜態(tài)庫(kù)中刪除文件objfile
-s 重置靜態(tài)庫(kù)文件索引
-v 創(chuàng)建文件冗余信息
-c 創(chuàng)建靜態(tài)庫(kù)文件
example:
/****************** hello.h **************/
void hello(void);
/****************** hello.cpp **************/
#include<IOStream>
#include"hello.h" using namespace std; void hello(void) { cout <<"Hello "<<endl;
}
/****************** main.cpp **************/
#include"hello.h"
int main(int argc,char *argv[])
{ hello(); return 0; }
1.編譯成靜態(tài)庫(kù)
無(wú)論靜態(tài)庫(kù),還是動(dòng)態(tài)庫(kù),都是由.o文件創(chuàng)建的。因此,我們必須將源程序hello.c通過(guò)gcc先編譯成.o文件。
hc@linux-v07j:~/weiming/tt> g++ -o hello.o -c hello.cpp
hc@linux-v07j:~/weiming/tt> ar cqs libHello.a hello.o
hc@linux-v07j:~/weiming/tt> ls
hello.cpp hello.h hello.o libHello.a main.cpp
2.鏈接
hc@linux-v07j:~/weiming/tt> g++ main.cpp libHello.a -o Out1 (g++ -o aOut main.cpp ./libHello.a 或者 g++ -o aOut main.cpp -L./ -lHello)
注意:如果hello() 里面還使用了其他的庫(kù)函數(shù)比如pthread_create,則最后生成Out1 時(shí)還需 -lpthread,但ar 時(shí)可以不用,只需要在 include 的頭文件中找到函數(shù)符號(hào)聲明即可,但最終生成可執(zhí)行文件時(shí)需要找到所有的符號(hào)定義。
hc@linux-v07j:~/weiming/tt> ls
hello.cpp hello.h hello.o libHello.a main.cpp Out1
hc@linux-v07j:~/weiming/tt> ldd Out1
linux-gate.so.1 => (0xffffe000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb7e36000)
libm.so.6 => /lib/libm.so.6 (0xb7e11000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0xb7e06000)
libc.so.6 => /lib/libc.so.6 (0xb7ce3000)
/lib/ld-linux.so.2 (0xb7f1b000)
二: 動(dòng)態(tài)庫(kù)
這類(lèi)庫(kù)的名字一般是libxxx.so;相對(duì)于靜態(tài)函數(shù)庫(kù),動(dòng)態(tài)函數(shù)庫(kù)在編譯的時(shí)候 并沒(méi)有被編譯進(jìn)目標(biāo)代碼中,你的程序執(zhí)行到相關(guān)函數(shù)時(shí)才調(diào)用該函數(shù)庫(kù)里的相應(yīng)函數(shù),因此動(dòng)態(tài)函數(shù)庫(kù)所產(chǎn)生的可執(zhí)行文件比較小。由于函數(shù)庫(kù)沒(méi)有被整合進(jìn)你的程序,而是程序運(yùn)行時(shí)動(dòng)態(tài)的申請(qǐng)并調(diào)用,所以程序的運(yùn)行環(huán)境中必須提供相應(yīng)的庫(kù)。動(dòng)態(tài)函數(shù)庫(kù)的改變并不影響你的程序,所以動(dòng)態(tài)函數(shù)庫(kù)的升級(jí)比較方便
不同的UNIX系統(tǒng),鏈接動(dòng)態(tài)庫(kù)方法,實(shí)現(xiàn)細(xì)節(jié)不一樣
編譯PIC型.o中間文件的方法一般是采用C語(yǔ)言編譯器的-KPIC或者-fpic選項(xiàng),有的UNIX版本C語(yǔ)言編譯器默認(rèn)帶上了PIC標(biāo)準(zhǔn).創(chuàng)建最終動(dòng)態(tài)庫(kù)的方法一般采用C語(yǔ)言編譯器的-G或者-shared選項(xiàng),或者直接使用工具ld創(chuàng)建。
最主要的是GCC命令行的一個(gè)選項(xiàng):
-shared 該選項(xiàng)指定生成動(dòng)態(tài)連接庫(kù)(讓連接器生成T類(lèi)型的導(dǎo)出符號(hào)表,有時(shí)候也生成弱連接W類(lèi)型的導(dǎo)出符號(hào)),不用該標(biāo)志外部程序無(wú)法連接。相當(dāng)于一個(gè)可執(zhí)行文件
-fPIC:表示編譯為位置獨(dú)立的代碼,不用此選項(xiàng)的話編譯后的代碼是位置相關(guān)的所以動(dòng)態(tài)載入時(shí)是通過(guò)代碼拷貝的方式來(lái)滿足不同進(jìn)程的需要,而不能達(dá)到真正代碼段共享的目的。(轉(zhuǎn)者注:共享庫(kù)各段的加載地址并沒(méi)有定死,可以加載到任意位置,因?yàn)橹噶钪袥](méi)有使用絕對(duì)地址(相對(duì)于鏈接后的可執(zhí)行文件各segment來(lái)說(shuō)),因此稱(chēng)為位置無(wú)關(guān)代碼)
-L.:表示要連接的庫(kù)在當(dāng)前目錄中
-ltest:編譯器查找動(dòng)態(tài)連接庫(kù)時(shí)有隱含的命名規(guī)則,即在給出的名字前面加上lib,后面加上.so來(lái)確定庫(kù)的名稱(chēng)
這里分別將源文件d1.c和d2.c編譯為動(dòng)態(tài)庫(kù)d1.so和d2.so.
/************ d1.h***************/
void print();
/*************** d1.cpp *******************/
#include <iostream>
#include "d1.h" using namespace std int p = 1; void print() { cout<< p <<endl;
}
/************ d2.h***************/
void print();
/*************** d2.cpp *******************/
#include <iostream>
#include "d2.h" using namespace std; int p = 2; void print() { cout<< p <<endl;
}
LINUX和其他gcc編譯器
g++ -fpic -c d1.cpp d2.cpp /* 編譯為.o為擴(kuò)展名的中間目標(biāo)文件d1.o,d2.o*/
g++ -shared -o libd1.so d1.o /*根據(jù)中間目標(biāo)文件d1.o創(chuàng)建動(dòng)態(tài)庫(kù)文件d1.so*/
g++ -shared -o libd2.so d2.o /*根據(jù)中間目標(biāo)文件d2.o創(chuàng)建動(dòng)態(tài)庫(kù)文件d2.so*/
或者直接一步到位
g++ -O -fpic -shared -o libd1.so d1.cpp
g++ -O -fpic -shared -o libd2.so d2.cpp
某些版本的gcc上也可以使用-G替換-shared選項(xiàng)
調(diào)用動(dòng)態(tài)庫(kù)
隱式調(diào)用動(dòng)態(tài)庫(kù)
/************** main.cpp *********************/
void print(); //或者用#include"d1.h"(#include"d2.h")替換
int main(int argc,char *argv[])
{ print(); }
#cp ./libd1.so libd.so (cp ./libd2.so libd.so )
# g++ -o dOut main.cpp ./libd.so (或者g++ -o dOut main.cpp -L./ -ld)
hc@linux-v07j:~/weiming/tt/dd> ldd dOut
linux-gate.so.1 => (0xffffe000)
libd.so => ./libd.so (0xb7f0f000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb7e2b000)
libm.so.6 => /lib/libm.so.6 (0xb7e06000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0xb7dfa000)
libc.so.6 => /lib/libc.so.6 (0xb7cd8000)
/lib/ld-linux.so.2 (0xb7f12000)
ldd模擬運(yùn)行一遍main,在運(yùn)行過(guò)程中做動(dòng)態(tài)鏈接,從而得知這個(gè)可執(zhí)行文件依賴(lài)于哪些共享庫(kù),每個(gè)共享庫(kù)都在什么路徑下,加載到進(jìn)程地址空間的什么地址。
總之,共享庫(kù)的搜索路徑由動(dòng)態(tài)鏈接器決定,從ld.so(8)的Man Page可以查到共享庫(kù)路徑的搜索順序:
1. 首先在環(huán)境變量LD_LIBRARY_PATH所記錄的路徑中查找。
2. 在程序鏈接時(shí)指定的 rpath 中查找,可以 readelf binfile | grep RPATH 。
3. 然后從緩存文件/etc/ld.so.cache中查找。這個(gè)緩存文件由/sbin/ldconfig命令讀取配置文件/etc/ld.so.conf 之后生成。
(也可以在 ld.so.conf.d 目錄下增加 *.conf 文件,里面寫(xiě)入庫(kù)路徑,在 ld.so.conf 中 include ld.so.conf.d/*.conf )
4. 如果上述步驟都找不到,則到默認(rèn)的系統(tǒng)路徑中查找,先是/usr/lib然后是/lib。
不同的UNIX所依賴(lài)的動(dòng)態(tài)庫(kù)查找路徑環(huán)境變量名稱(chēng)各不相同
UNIX版本 動(dòng)態(tài)庫(kù)查找路徑環(huán)境變量
AIX LIB_PATH
LINUX LD_LIBRARY_PATH
HP_UNIX PAHT
SCO UNIX LD_LIBRARY_PATH
動(dòng)態(tài)鏈接庫(kù)取代靜態(tài)庫(kù)的好處之一就是可以隨時(shí)升級(jí)庫(kù)的內(nèi)容。
當(dāng)動(dòng)態(tài)庫(kù)被接口完全相同的庫(kù)文件取代后,可執(zhí)行程序能迅速的切換到新動(dòng)態(tài)庫(kù)中代碼,省去了編譯的麻煩。
顯式調(diào)用動(dòng)態(tài)庫(kù)
顯式調(diào)用動(dòng)態(tài)庫(kù),編譯時(shí)無(wú)需庫(kù)文件,執(zhí)行時(shí)動(dòng)態(tài)可存儲(chǔ)于任意位置,庫(kù)里共享對(duì)象必須先申請(qǐng)后使用,不同動(dòng)態(tài)庫(kù)版本,只要其共享對(duì)象接口相同,就可以直接動(dòng)態(tài)加載。注意添加 "-ldl" 編譯參數(shù)。
//打開(kāi)動(dòng)態(tài)庫(kù)
#include<dlfcn.h>
void *dlopen(const char * pathname,int mode); //獲取動(dòng)態(tài)庫(kù)對(duì)象地址 include<dlfcn.h>
void *dlsym(void *handle,const char *name); //錯(cuò)誤檢測(cè) include<dlfcn.h>
char *dlerror(vid); //關(guān)閉動(dòng)態(tài)庫(kù) include<dlfcn.h>
int dlclose(void * handle);
動(dòng)態(tài)庫(kù)的加載或多或少會(huì)占用一定的系統(tǒng)資源,比如內(nèi)存等。因此當(dāng)不需要或者一段時(shí)間內(nèi)不需要共享動(dòng)態(tài)庫(kù)時(shí)就要卸載之。函數(shù)dlclose關(guān)閉參數(shù)handle所指向的動(dòng)態(tài)庫(kù),卸載其所占的內(nèi)存等資源,此調(diào)用后參數(shù)handle無(wú)效。
實(shí)際上,由于動(dòng)態(tài)庫(kù)可能同時(shí)被多個(gè)進(jìn)程共享,當(dāng)一個(gè)進(jìn)程指向dlclose時(shí),資源并不馬上被卸載,只有當(dāng)全部進(jìn)程都宣布關(guān)閉動(dòng)態(tài)庫(kù)后,操作系統(tǒng)才開(kāi)始回收動(dòng)態(tài)庫(kù)資源。
總結(jié):
編譯靜態(tài)庫(kù)時(shí)先使用-c選項(xiàng),再利用ar工具產(chǎn)生.編譯動(dòng)態(tài)庫(kù)的方式依不同版本的UNXI而定。隱式調(diào)用動(dòng)態(tài)庫(kù)與靜態(tài)庫(kù)的用法相一致,而顯示調(diào)用動(dòng)態(tài)庫(kù)則需要借助動(dòng)態(tài)加載共享庫(kù)函數(shù)族。
隱式調(diào)用動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)使用方法一致,使用靜態(tài)庫(kù)和使用動(dòng)態(tài)庫(kù)編譯成目標(biāo)程序使用的gcc命令完全一樣,那當(dāng)靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)同名時(shí),gcc命令會(huì)使用哪個(gè)庫(kù)文件呢?
通過(guò)測(cè)試可以發(fā)現(xiàn),當(dāng)靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)同名時(shí), gcc命令將優(yōu)先使用動(dòng)態(tài)庫(kù).為了確保使用的是靜態(tài)庫(kù), 編譯時(shí)可以加上 -static 選項(xiàng),因此多第三方程序?yàn)榱舜_保在沒(méi)有相應(yīng)動(dòng)態(tài)庫(kù)時(shí)運(yùn)行正常,喜歡在編譯最后應(yīng)用程序時(shí)加入-static






