Linux下动态库(.so)和静态库(.a)
linux下有兩種庫:動態(tài)庫和靜態(tài)庫(共享庫)
二者的不同點(diǎn)在于代碼被載入的時(shí)刻不同。
靜態(tài)庫的代碼在編譯過程中已經(jīng)被載入可執(zhí)行程序,因此體積比較大。
動態(tài)庫(共享庫)的代碼在可執(zhí)行程序運(yùn)行時(shí)才載入內(nèi)存,在編譯過程中僅簡單的引用,因此代碼體積比較小。
不同的應(yīng)用程序如果調(diào)用相同的庫,那么在內(nèi)存中只需要有一份該動態(tài)庫(共享庫)的實(shí)例。
靜態(tài)庫和動態(tài)庫的最大區(qū)別,靜態(tài)情況下,把庫直接加載到程序中,而動態(tài)庫鏈接的時(shí)候,它只是保留接口,將動態(tài)庫與程序代碼獨(dú)立,這樣就可以提高代碼的可復(fù)用度,和降低程序的耦合度。
靜態(tài)庫在程序編譯時(shí)會被連接到目標(biāo)代碼中,程序運(yùn)行時(shí)將不再需要該靜態(tài)庫。
動態(tài)庫在程序編譯時(shí)并不會被連接到目標(biāo)代碼中,而是在程序運(yùn)行是才被載入,因此在程序運(yùn)行時(shí)還需要動態(tài)庫存在
?
一? 靜態(tài)庫
這類庫的名字一般是libxxx.a;利用靜態(tài)函數(shù)庫編譯成的文件比較大,因?yàn)檎麄€(gè) 函數(shù)庫的所有數(shù)據(jù)都會被整合進(jìn)目標(biāo)代碼中,他的優(yōu)點(diǎn)就顯而易見了,即編譯后的執(zhí)行程序不需要外部的函數(shù)庫支持,因?yàn)樗惺褂玫暮瘮?shù)都已經(jīng)被編譯進(jìn)去了。當(dāng)然這也會成為他的缺點(diǎn),因?yàn)槿绻o態(tài)函數(shù)庫改變了,那么你的程序必須重新編譯。
靜態(tài)庫的代碼在編譯時(shí)鏈接到應(yīng)用程序中,因此編譯時(shí)庫文件必須存在,并且需要通過“-L”參數(shù)傳遞給編譯器,應(yīng)用程序在開始執(zhí)行時(shí),庫函數(shù)代碼將隨程序一起調(diào)入進(jìn)程內(nèi)存段直到進(jìn)程結(jié)束,其執(zhí)行過程不需要原靜態(tài)庫存在。
在UNIX中,使用ar命令創(chuàng)建或者操作靜態(tài)庫
ar???? archivefile objfile
archivefile:archivefile是靜態(tài)庫的名稱
objfile:objfile是已.o為擴(kuò)展名的中間目標(biāo)文件名,可以多個(gè)并列
參數(shù)??????? 意義
-r??????????? 將objfile文件插入靜態(tài)庫尾或者替換靜態(tài)庫中同名文件
-x??????????? 從靜態(tài)庫文件中抽取文件objfile
-t???????????? 打印靜態(tài)庫的成員文件列表
-d??????????? 從靜態(tài)庫中刪除文件objfile
-s?????????? 重置靜態(tài)庫文件索引
-v????????????創(chuàng)建文件冗余信息
-c??????????? 創(chuàng)建靜態(tài)庫文件
?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)庫
無論靜態(tài)庫,還是動態(tài)庫,都是由.o文件創(chuàng)建的。因此,我們必須將源程序hello.c通過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)
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)
?
?
二: 動態(tài)庫
這類庫的名字一般是libxxx.so;相對于靜態(tài)函數(shù)庫,動態(tài)函數(shù)庫在編譯的時(shí)候 并沒有被編譯進(jìn)目標(biāo)代碼中,你的程序執(zhí)行到相關(guān)函數(shù)時(shí)才調(diào)用該函數(shù)庫里的相應(yīng)函數(shù),因此動態(tài)函數(shù)庫所產(chǎn)生的可執(zhí)行文件比較小。由于函數(shù)庫沒有被整合進(jìn)你的程序,而是程序運(yùn)行時(shí)動態(tài)的申請并調(diào)用,所以程序的運(yùn)行環(huán)境中必須提供相應(yīng)的庫。動態(tài)函數(shù)庫的改變并不影響你的程序,所以動態(tài)函數(shù)庫的升級比較方便
不同的UNIX系統(tǒng),鏈接動態(tài)庫方法,實(shí)現(xiàn)細(xì)節(jié)不一樣
編譯PIC型.o中間文件的方法一般是采用C語言編譯器的-KPIC或者-fpic選項(xiàng),有的UNIX版本C語言編譯器默認(rèn)帶上了PIC標(biāo)準(zhǔn).創(chuàng)建最終動態(tài)庫的方法一般采用C語言編譯器的-G或者-shared選項(xiàng),或者直接使用工具ld創(chuàng)建。
最主要的是GCC命令行的一個(gè)選項(xiàng):
-shared 該選項(xiàng)指定生成動態(tài)連接庫(讓連接器生成T類型的導(dǎo)出符號表,有時(shí)候也生成弱連接W類型的導(dǎo)出符號),不用該標(biāo)志外部程序無法連接。相當(dāng)于一個(gè)可執(zhí)行文件
-fPIC:表示編譯為位置獨(dú)立的代碼,不用此選項(xiàng)的話編譯后的代碼是位置相關(guān)的所以動態(tài)載入時(shí)是通過代碼拷貝的方式來滿足不同進(jìn)程的需要,而不能達(dá)到真正代碼段共享的目的。
-L.:表示要連接的庫在當(dāng)前目錄中
-ltest:編譯器查找動態(tài)連接庫時(shí)有隱含的命名規(guī)則,即在給出的名字前面加上lib,后面加上.so來確定庫的名稱
LD_LIBRARY_PATH:這個(gè)環(huán)境變量指示動態(tài)連接器可以裝載動態(tài)庫的路徑。
?當(dāng)然如果有root權(quán)限的話,可以修改/etc/ld.so.conf文件,然后調(diào)用 /sbin/ldconfig來達(dá)到同樣的目的,不過如果沒有root權(quán)限,那么只能采用輸出LD_LIBRARY_PATH的方法了。
這里分別將源文件d1.c和d2.c編譯為動態(tài)庫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)建動態(tài)庫文件d1.so*/
g++ -shared -o libd2.so d2.o? ??/*根據(jù)中間目標(biāo)文件d2.o創(chuàng)建動態(tài)庫文件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)用動態(tài)庫
?隱身調(diào)用動態(tài)庫
?
/************** 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)? //這個(gè)動態(tài)庫文件比靜態(tài)編譯多的
??????? 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)
在上例中,動態(tài)庫libd.so與執(zhí)行程序在同一目錄下,如果將libd.so移走再執(zhí)行程序,程序?qū)⒉荒苷?zhí)行。
當(dāng)需要載入動態(tài)庫代碼時(shí),UNIX會按照某種路徑查找動態(tài)庫
通知UNIX系統(tǒng)動態(tài)庫的正確位置有如下兩種方法.,
1)帶編譯路徑
#g++ -o dOut main.cpp ./libd.so (或者g++ -o dOut main.cpp -L./ -ld)
當(dāng)執(zhí)行程序時(shí),程序會自動在當(dāng)前路徑下操作動態(tài)庫libd.so
2)更改環(huán)境變量
#LD_LIBPARY_PATH=./
#export LD_LIBPARY_PATH
不同的UNIX所依賴的動態(tài)庫查找路徑環(huán)境變量名稱各不相同
UNIX版本????????????? 動態(tài)庫查找路徑環(huán)境變量
AIX???????????????? LIB_PATH
LINUX?????????? LD_LIBPARY_PATH
HP_UNIX????? PAHT
SCO UNIX???? LD_LIBPARY_PAHT
?
動態(tài)鏈接庫取代靜態(tài)庫的好處之一就是可以隨時(shí)升級庫的內(nèi)容。
?當(dāng)動態(tài)庫被接口完全相同的庫文件取代后,可執(zhí)行程序能迅速的切換到新動態(tài)庫中代碼,省去了編譯的麻煩。
例如將libd2.so換成libd.so
?
顯示調(diào)用動態(tài)庫
顯示調(diào)用動態(tài)庫,編譯時(shí)無需庫文件,執(zhí)行時(shí)動態(tài)可存儲于任意位置,庫里共享對象必須先申請后使用,不同動態(tài)庫版本,只要其共享對象接口相同,就可以直接動態(tài)加載。
//打開動態(tài)庫 #include<dlfcn.h> void *dlopen(const char * pathname,int mode);//獲取動態(tài)庫對象地址 include<dlfcn.h> void *dlsym(void *handle,const char *name);//錯(cuò)誤檢測 include<dlfcn.h> char *dlerror(vid);//關(guān)閉動態(tài)庫 include<dlfcn.h> int dlclose(void * handle);動態(tài)庫的加載或多或少會占用一定的系統(tǒng)資源,比如內(nèi)存等。因此當(dāng)不需要或者一段時(shí)間內(nèi)不需要共享動態(tài)庫時(shí)就要卸載之。函數(shù)dlclose關(guān)閉參數(shù)handle所指向的動態(tài)庫,卸載其所占的內(nèi)存等資源,此調(diào)用后參數(shù)handle無效。
實(shí)際上,由于動態(tài)庫可能同時(shí)被多個(gè)進(jìn)程共享,當(dāng)一個(gè)進(jìn)程指向dlclose時(shí),資源并不馬上被卸載,只有當(dāng)全部進(jìn)程都宣布關(guān)閉動態(tài)庫后,操作系統(tǒng)才開始回收動態(tài)庫資源。
?
?
?
總結(jié):
編譯靜態(tài)庫時(shí)先使用-c選項(xiàng),再利用ar工具產(chǎn)生.編譯動態(tài)庫的方式依不同版本的UNXI而定。隱式調(diào)用動態(tài)庫與靜態(tài)庫的用法相一致,而顯示調(diào)用動態(tài)庫則需要借助動態(tài)加載共享庫函數(shù)族。
隱式調(diào)用動態(tài)庫和靜態(tài)庫使用方法一致,使用靜態(tài)庫和使用動態(tài)庫編譯成目標(biāo)程序使用的gcc命令完全一樣,那當(dāng)靜態(tài)庫和動態(tài)庫同名時(shí),gcc命令會使用哪個(gè)庫文件呢?
通過測試可以發(fā)現(xiàn),當(dāng)靜態(tài)庫和動態(tài)庫同名時(shí), gcc命令將優(yōu)先使用動態(tài)庫.為了確保使用的是靜態(tài)庫, 編譯時(shí)可以加上 -static? 選項(xiàng),因此多第三方程序?yàn)榱舜_保在沒有相應(yīng)動態(tài)庫時(shí)運(yùn)行正常,喜歡在編譯最后應(yīng)用程序時(shí)加入-static
總結(jié)
以上是生活随笔為你收集整理的Linux下动态库(.so)和静态库(.a)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Apache PHP-fpm
- 下一篇: linux svn