當(dāng)前位置:首頁(yè) > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 講師博文 > Linux下動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)的制作及使用
在實(shí)際的開(kāi)發(fā)過(guò)程中,編寫程序往往都需要依賴很多基礎(chǔ)的底層庫(kù),比方說(shuō)平時(shí)用的較多的標(biāo)準(zhǔn)C庫(kù),數(shù)學(xué)庫(kù)等等;我們會(huì)頻繁的使用這些庫(kù)里的函數(shù),這些函數(shù)大多數(shù)都是前人為我們寫好的,所以值得慶幸的是我們的工作不必從零開(kāi)始,我們要做的只是在恰當(dāng)?shù)奈恢谜{(diào)用合適的庫(kù)函數(shù)去實(shí)現(xiàn)相應(yīng)的功能,充分利用前人的勞動(dòng)成果,就是“站在巨人的肩膀上”。本文主要簡(jiǎn)述Linux下庫(kù)的制作以及使用方法。
一、什么是庫(kù)
庫(kù)從本質(zhì)上來(lái)說(shuō)是一種可執(zhí)行代碼的二進(jìn)制格式,可以被載入內(nèi)存中執(zhí)行。根據(jù)鏈接時(shí)期的不同,庫(kù)又有:靜態(tài)庫(kù)和共享庫(kù)(動(dòng)態(tài)庫(kù))二者的不同點(diǎn)在于代碼被載入的時(shí)刻不同。
靜態(tài)庫(kù)的代碼在編譯過(guò)程中已經(jīng)被載入可執(zhí)行程序,因此體積較大。
共享庫(kù)的代碼是在可執(zhí)行程序運(yùn)行時(shí)才載入內(nèi)存的,在編譯過(guò)程中僅簡(jiǎn)單的引用,因此代碼體積較小。
二、初識(shí)靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)
1.靜態(tài)函數(shù)庫(kù)
這類庫(kù)的名字一般是libxxx.a,xxx為庫(kù)的名字。利用靜態(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ù)改變了,那么你的程序必須重新編譯。
2.動(dòng)態(tài)函數(shù)庫(kù)
這類庫(kù)的名字一般是libxxx.M.N.so,同樣的xxx為庫(kù)的名字,M是庫(kù)的主版本號(hào),N是庫(kù)的副版本號(hào)。當(dāng)然也可以不要版本號(hào),但名字必須有。相對(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í)比較方便。linux系統(tǒng)有幾個(gè)重要的目錄存放相應(yīng)的函數(shù)庫(kù),如/lib /usr/lib。
三、靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)的比較
靜態(tài)庫(kù)其實(shí)從某種意義上來(lái)說(shuō)只不過(guò)它操作的對(duì)象是目標(biāo)代碼而不是源碼而已。因?yàn)殪o態(tài)庫(kù)被鏈接后庫(kù)就直接嵌入可執(zhí)行文件中了,這樣就帶來(lái)了兩個(gè)問(wèn)題。
(1)首先就是系統(tǒng)空間被浪費(fèi)了。這是顯而易見(jiàn)的,想象一下,如果多個(gè)程序鏈接了同一個(gè)庫(kù),則每一個(gè)生成的可執(zhí)行文件就都會(huì)有一個(gè)庫(kù)的副本,必然會(huì)浪費(fèi)系統(tǒng)空間。
(2)再者,一旦發(fā)現(xiàn)了庫(kù)中有bug,挽救起來(lái)就比較麻煩了。必須一一把鏈接該庫(kù)的程序找出來(lái),然后重新編譯。
而動(dòng)態(tài)庫(kù)的出現(xiàn)正彌補(bǔ)了靜態(tài)庫(kù)的以上弊端。因?yàn)閯?dòng)態(tài)庫(kù)是在程序運(yùn)行時(shí)被鏈接的,所以磁盤上只須保留一份副本,因此節(jié)約了磁盤空間。如果發(fā)現(xiàn)了bug或要升級(jí)也很簡(jiǎn)單,只要用新的庫(kù)把原來(lái)的替換掉就行了。
但是靜態(tài)庫(kù)也有自己的優(yōu)點(diǎn):編譯后的執(zhí)行程序不需要外部的函數(shù)庫(kù)支持,因?yàn)樗惺褂玫暮瘮?shù)都已經(jīng)被編譯進(jìn)去了。
四、如何判斷一個(gè)程序有沒(méi)有鏈接動(dòng)態(tài)庫(kù)
(1)file命令
file程序是用來(lái)判斷文件類型的,啥文件一看都清楚明了。
(2)ldd命令
看動(dòng)態(tài)庫(kù),如果目標(biāo)程序沒(méi)有鏈接動(dòng)態(tài)庫(kù),則打印“not a dynamic executable” (不是動(dòng)態(tài)可執(zhí)行文件)
五、靜態(tài)庫(kù)的制作
(1) 為pr1和pr2生成object文件
gcc -O -c pr1.c pr2.c
(2) ls
(3) 鏈接靜態(tài)庫(kù)
為了在編譯程序中正確找到庫(kù)文件,靜態(tài)庫(kù)必須按照 lib[name].a 的規(guī)則命名,如下例中[name]=pr.
ar參數(shù)意義:
c: create的意思
r:在庫(kù)中插入模塊(替換)。當(dāng)插入的模塊名已經(jīng)在庫(kù)中存在,則替換同名的模塊。
s:寫入一個(gè)目標(biāo)文件索引到庫(kù)中,或者更新一個(gè)存在的目標(biāo)文件索引。
v:該選項(xiàng)用來(lái)顯示執(zhí)行操作選項(xiàng)的附加信息。
t:顯示庫(kù)的模塊表清單。一般只顯示模塊名。
ar -crsv libpr.a pr1.o pr2.o
ar -t libpr.a //顯示靜態(tài)庫(kù)所依賴的文件
(4) 編譯鏈接選項(xiàng)
-L 及-l 參數(shù)放在后面.其中,-L 加載庫(kù)文件路徑,-l 指明庫(kù)文件名字.
gcc -o main main.c -L./ -lpr //生成main
-I后面接頭文件 (大寫的i)
-L后面接庫(kù)文件路徑路徑
-l后面接庫(kù)文件名,除了“lib”和“.a”部分,全名為libpr.a
(5)執(zhí)行目標(biāo)程序
./main
六、動(dòng)態(tài)庫(kù)的制作
注意,和動(dòng)態(tài)庫(kù)相關(guān)的路徑搜索問(wèn)題可以認(rèn)為分鏈接時(shí)的搜索 和 運(yùn)行時(shí)加載的搜索。鏈接時(shí)的搜索就是”-L”,比較簡(jiǎn)單直接,我們重點(diǎn)講的是運(yùn)行時(shí)的加載搜索。
(1)生成動(dòng)態(tài)庫(kù) xxx.so
gcc -fPIC -Wall -c pr1.c
PIC告訴編譯器產(chǎn)生與位置無(wú)關(guān)代碼(Position-Independent Code), 則產(chǎn)生的代碼中,沒(méi)有絕對(duì)地址,全部使用相對(duì)地址,故而代碼可以被加載器加載到內(nèi)存的任意位置,都可以正確的執(zhí)行。這正是共享庫(kù)所要求的,共享庫(kù)被加載時(shí),在內(nèi)存的位置不是固定的。
gcc -shared -o libpr.so pr1.o
or use one line:
gcc -O -fPIC -shared -o libpr.so pr1.c
(2)編譯時(shí)調(diào)用動(dòng)態(tài)庫(kù)
gcc -o test main.c –L. -lpr
采用該方法執(zhí)行會(huì)報(bào)告./test: error while loading shared libraries: libpr.so: cannot open shared object file: No such file or directory
原因:因?yàn)樵趧?dòng)態(tài)函數(shù)庫(kù)使用時(shí),會(huì)查找/usr/lib、/lib目錄下的動(dòng)態(tài)函數(shù)庫(kù),而此時(shí)我們生成的庫(kù)不在里邊。
這個(gè)時(shí)候有好幾種方法可以讓他成功運(yùn)行:
(1)直接簡(jiǎn)單的方法就是把so拉到/usr/lib或/lib中去,但這好像有點(diǎn)污染環(huán)境吧。需要root權(quán)限,在別人的電腦上會(huì)很麻煩;會(huì)把系統(tǒng)目錄弄得混亂。
(2)新建并編輯/etc/ld.so.conf.d/my.conf文件,加入庫(kù)所在目錄的路徑,執(zhí)行l(wèi)dconfig命令更新ld.so.cache文件但是需要root權(quán)限。
(3)export LD_LIBRARY_PATH=/tmp
不過(guò)這樣export 只對(duì)當(dāng)前shell有效,當(dāng)另開(kāi)一個(gè)shell時(shí)候,又要重新設(shè)置?梢园裡xport LD_LIBRARY_PATH=/tmp 語(yǔ)句寫到 ~/.bashrc中,這樣就對(duì)當(dāng)前用戶有效了,寫到/etc/bashrc中就對(duì)所有用戶有效了。
echo $LD_LIBRARY_PATH
不過(guò)LD_LIBRARY_PATH的設(shè)定作用是全局的,過(guò)多的使用可能會(huì)影響到其他應(yīng)用程序的運(yùn)行,所以多用在調(diào)試。
小結(jié):
總而言之,靜態(tài)庫(kù)是以空間換時(shí)間,動(dòng)態(tài)庫(kù)是以時(shí)間換空間。無(wú)論你是在Linux平臺(tái)還是Windows平臺(tái)下做開(kāi)發(fā),庫(kù)的使用都大同小異。熟練的使各種庫(kù),會(huì)給我們帶來(lái)許多便利,減少工作的負(fù)擔(dān)加快工程的進(jìn)度,從此升職,加薪不是夢(mèng),希望對(duì)你有所幫助。