一也切、簡介
實(shí)際開發(fā)工程中划滋,一般會有很多函數(shù)只是聲明饵筑,而找不到實(shí)現(xiàn)的代碼,因?yàn)槟切?shí)現(xiàn)代碼已經(jīng)編譯成庫了处坪。在Linux系統(tǒng)中.o根资、.a、*.so文件都是Linux下的程序函數(shù)庫同窘,即編譯好的可供其他程序使用的代碼和數(shù)據(jù)玄帕,一般來講:
- ==.o==:是目標(biāo)文件,相當(dāng)于windows中的.obj文件(動態(tài)加載函數(shù)庫)想邦;
- ==.so==:為共享庫裤纹,是shared object,用于動態(tài)鏈接的丧没,和dill差不多(共享函數(shù)庫)鹰椒;
- ==.a==:為靜態(tài)庫,是好多個.o合在一起呕童,用于靜態(tài)鏈接(靜態(tài)函數(shù)庫)漆际;
- ==.la==:為libtool自動生成的一些共享庫,vi編輯查看拉庵,主要記錄了一些配置信息灿椅;
這樣做有以下優(yōu)點(diǎn):程序模塊化,容易重新編譯钞支,方便升級茫蛹。
庫文件在鏈接(靜態(tài)庫和共享庫)和運(yùn)行(僅限于使用共享庫的程序)時被使用,其搜索路勁是系統(tǒng)中進(jìn)行設(shè)置的烁挟,一般Linux把/lib和/usr/lib兩個目錄作為默認(rèn)的庫搜索路徑婴洼,所以使用這兩個目錄中的庫時,不需要進(jìn)行設(shè)置搜索路徑即可直接使用撼嗓。
假設(shè)當(dāng)前目錄下有這些源文件:main.c func.c func.h柬采,其中main.c要調(diào)用func.c中的函數(shù)。以下分別是Linux下將func.c生成靜態(tài)庫和動態(tài)庫的方法且警,以及如何使用生成的靜態(tài)庫和動態(tài)庫粉捻。
二、靜態(tài)鏈接庫
2.1 靜態(tài)庫的特點(diǎn)
靜態(tài)鏈接庫可以將代碼進(jìn)行封裝斑芜,具有如下特點(diǎn):
- 靜態(tài)函數(shù)庫實(shí)際上是簡單的普通目標(biāo)文件(*.o)的集合肩刃;
- 靜態(tài)函數(shù)庫在可執(zhí)行程序鏈接時就加入到可執(zhí)行代碼中,在物理上成為可執(zhí)行程序的一部分;
- 靜態(tài)函數(shù)庫鏈接生成的程序盈包,在哪里都可以運(yùn)行沸呐,無需該靜態(tài)函數(shù)庫的支持;
- 相對于動態(tài)函數(shù)庫鏈接生成的程序呢燥,靜態(tài)函數(shù)庫鏈接生成的可執(zhí)行程序文件較大崭添;
2.2 靜態(tài)庫的優(yōu)點(diǎn)
相比源代碼來說,靜態(tài)鏈接庫有如下特點(diǎn):
- 可以重用以前的程序模塊叛氨;
- 開發(fā)者可以對源代碼保密呼渣;
- 允許程序員不用重新編譯代碼而直接把程序link起來,節(jié)省了重新編譯代碼的時間(該優(yōu)勢目前已不明顯)力试;
- 理論上將徙邻,使用elf格式的靜態(tài)庫函數(shù)生成的代碼可以比使用共享函數(shù)庫生成的程序運(yùn)行速度快。
2.3 靜態(tài)庫的制作
【命令格式】:
在linux環(huán)境中畸裳,使用ar命令創(chuàng)建靜態(tài)庫文件(創(chuàng)建嵌入式靜態(tài)庫缰犁,可使用arm-linux-ar命令),該命令格式如下:
ar [-]{dmpqrtx}[abcfilNoPsSuvV] [membername] [count] archive files…
arm-linux-ar [-]{dmpqrtx}[abcfilNoPsSuvV] [membername] [count] archive files…
- ==archive==:定義庫的名稱怖糊;
- ==files== :是庫文件中包含的目標(biāo)文件的清單帅容,用空格分隔每個文件;
【命令選項(xiàng)】:
- -a :把新的目標(biāo)文件(*.o)添加到靜態(tài)庫文件中現(xiàn)有文件之后;
- -b :把新的目標(biāo)文件(*.o)添加到靜態(tài)庫文件中現(xiàn)有文件之前伍伤;
- -d :從指定的靜態(tài)庫文件中刪除文件并徘;
- -m:把文件移動到指定的靜態(tài)庫文件中;
- -p :把靜態(tài)庫文件中指定的文件輸出到標(biāo)準(zhǔn)輸出扰魂;
- -q :快速地把文件追加到靜態(tài)庫文件中麦乞;
- -r :把文件插入到靜態(tài)庫文件中;
- -t :顯示靜態(tài)庫文件中文件的列表劝评;
- -x :從靜態(tài)庫文件中提取文件姐直;
- -v :使用詳細(xì)模式
如下面創(chuàng)建了一個libapue.a庫文件,然后可以用t選項(xiàng)顯示包含在庫中的文件:
$ ar –r libapue.a error.o errorlog.o lockreg.o
++創(chuàng)建庫文件之后蒋畜,可以創(chuàng)建這個靜態(tài)庫文件的索引來幫助提高和庫連接的其他程序的編譯速度声畏。使用ranlib程序創(chuàng)建庫的索引,索引存放在庫文件的內(nèi)部姻成。++
$ ranlib libapue.a
++用nm程序可以顯示存檔文件的索引插龄,它也可以顯示目標(biāo)文件的符號:++
$ nm libapue.a | more
$ nm error.o | more
2.4 靜態(tài)庫的使用
靜態(tài)庫的使用方法,可以采用如下三種中的一種(-static可以不用):
$ gcc test.c –o test libapue.a
$ gcc test.c –o –L. -lfunc –static
$ gcc test.c –o –L./ -lfunc -static
【使用說明】
- 使用靜態(tài)庫生成的可執(zhí)行文件放在目標(biāo)板中可以直接運(yùn)行科展;
- 使用靜態(tài)庫時均牢,需用-L指定靜態(tài)庫的位置,如上面指定為當(dāng)前目錄:-L./才睹;
- 使用靜態(tài)庫時膨处,需用-l指定靜態(tài)庫的庫名见秤,如上面的-lfunc砂竖。
【完整使用實(shí)例】:
$ gcc func.c –c –o func.o //生成目標(biāo)文件
$ ar crv libfunc.a func.o //生成靜態(tài)庫
$ gcc main.c –static –L./ –lfunc –o main //使用靜態(tài)庫
$ ./main //執(zhí)行文件
三真椿、動態(tài)鏈接庫
3.1 動態(tài)庫的簡介
動態(tài)庫是在可執(zhí)行程序啟動時加載到執(zhí)行程序中,可以被多個執(zhí)行程序共享使用乎澄,使用動態(tài)庫編譯生成的程序相對較小突硝,但運(yùn)行時需要庫文件支持;
動態(tài)庫的鏈接時路徑和運(yùn)行時路徑:現(xiàn)代鏈接器在處理動態(tài)庫時將鏈接時路徑(Link-time path)和運(yùn)行時路徑(Run-time path)分開置济,用戶可通過-L指定鏈接時解恰,庫文件的路徑;通過-R(或-rpath)指定程序運(yùn)行時浙于,庫文件的路徑护盈,大大提高了庫應(yīng)用的靈活性。比如我們做嵌入式移植時#arm-linux-gcc $(CFLAGS) –o target –L/work/lib/zlib/ -llibz-1.2.3 (work/lib/zlib下是交叉編譯好的zlib庫)羞酗,將target編譯好后我們只要把zlib庫拷貝到開發(fā)板的系統(tǒng)默認(rèn)路徑下即可腐宋。或者通過- rpath(或-R )檀轨、LD_LIBRARY_PATH指定查找路徑胸竞。
3.2 動態(tài)庫的命名
每個動態(tài)庫(*.so)文件都有個特殊的名字,叫做“soname”参萄,它必須以“l(fā)ib”作為前綴卫枝,然后是函數(shù)庫名,然后是“.so”讹挎,最后是版本號信息校赤;不過有個特例,就是非常底層的C庫函數(shù)都不是以lib開頭命名的筒溃;每個動態(tài)庫(*.so)文件都有一個真正的名字马篮,叫做“real name”,它是包含真正庫函數(shù)代碼的文件铡羡;真名有一個主版本號和一個發(fā)行版本號外积蔚,還有一個名字是編譯器編譯的時候需要的函數(shù)庫的名字,這個就是簡單的soname名字烦周,它不包含任何版本號信息:
- soname:必須的格式:lib+函數(shù)庫名+.so+版本號信息尽爆,如libreadline.so.3
- real name:也就是真正的名字,有主版本號和發(fā)行版本號读慎;
- 編譯名字:編譯器編譯的時候需要的函數(shù)庫的名字就是不包含版本號信息的soname漱贱,如上面的libreadlie.so.3去掉后面的“.3”即可。
管理共享函數(shù)庫的關(guān)鍵是區(qū)分好這些名字夭委,++當(dāng)可執(zhí)行程序需要在自己的程序中列出這些他們需要的共享庫函數(shù)的時候幅狮,它只要用soname就可以了;反過來,當(dāng)要創(chuàng)建一個新的共享函數(shù)庫的時候崇摄,只要指定一個特定的文件名擎值,其中包含很細(xì)節(jié)的版本信息;當(dāng)安裝一個新版本的函數(shù)庫的時候逐抑,只要先將這些函數(shù)庫文件拷貝到一些特定的目錄中鸠儿,運(yùn)行l(wèi)dconfig這個命令即可++(ldconfig檢查已存在的庫文件,然后創(chuàng)建soname的符號鏈接到真正的函數(shù)庫厕氨,同時設(shè)置/etc/ld.so.cache這個緩沖文件)进每。
3.3 動態(tài)庫的制作
由于動態(tài)鏈接庫的共享特性,它們不會被拷貝到可執(zhí)行文件中命斧。在編譯的時候田晚,編譯器只會做一些函數(shù)名之類的檢查,在程序運(yùn)行的時候国葬,被調(diào)用的動態(tài)鏈接庫函數(shù)被安置在內(nèi)存的某個地方贤徒,所有調(diào)用它的程序?qū)⒅赶蜻@個代碼段。因此胃惜,這些代碼必須使用相對地址泞莉,而不是絕對地址。那么在編譯的時候船殉,我們需要使用地址無關(guān)選項(xiàng)(Position Independent Code:PIC)“-fpic”來告訴編譯器鲫趁,這些對象文件是用來做動態(tài)鏈接庫的。創(chuàng)建共享庫的方法:
$ gcc -fpic error.c -c -o error.o
$ gcc -fpic errorlog.c -c -o errorlog.o
$ gcc -fpic lockreg.c -c -o lockreg.o
$ gcc -shared -o libapue.so error.o errorlog.o lockreg.o
也可以使用如下方法:
$ gcc -shared -fpic error.c errorlog.c lockreg.c -o libapue.so
- -shared選項(xiàng)是告訴編譯器這是要建立動態(tài)鏈接庫利虫;這與靜態(tài)鏈接庫的建立很不一樣挨厚,這里使用的gcc命令而不是ar,同時庫的后綴名為*.so糠惫;
- -fpic選項(xiàng)告訴編譯器該代碼為位置無關(guān)碼疫剃,不用此選項(xiàng)的話編譯后的代碼是位置相關(guān)的,所以動態(tài)載入時是通過代碼拷貝的方式來滿足不同進(jìn)程的需要硼讽,而不能達(dá)到真正代碼段共享的目的巢价;
編譯共享庫的方法(假設(shè)庫文件在當(dāng)前目錄),可采用如下三種中的一種:
$ gcc test.c -o test -L. -lapue
$ gcc test.c -o test -L./ -lapue
$ gcc test.c -o test libapue.so
這樣就編譯出了不包含函數(shù)代碼的可執(zhí)行文件了,但是當(dāng)你運(yùn)行生成的可執(zhí)行文件時,動態(tài)加載器無法動態(tài)加載libapue.so文件砸紊。那如何才能讓動態(tài)加載器發(fā)現(xiàn)庫文件呢?有如下兩種方法:
- 在環(huán)境變量LD_LIBRARY_PATH中指明庫的搜索路徑碉克;
export LD_LIBRARY_PATH=”$(LD_LIBRARY_PATH):.” #添加當(dāng)前路勁
- 配置/etc/ld.so.conf文件(推薦且簡單的方法):一般應(yīng)用程序的庫文件不與系統(tǒng)庫文件放在同一個目錄下,一般把應(yīng)用程序的共享庫文件放在/usr/local/lib下并齐,新建一個屬于自己的目錄apue漏麦,然后把自己的libapue.so復(fù)制過去就行了客税;同時在/etc/ld.so.conf中新增一行:
/usr/local/lib/apue
以后在編譯程序時加上編譯選項(xiàng):-L/usr/local/lib/apue –lapue;針對具體的可執(zhí)行文件撕贞,可以通過ldd命令查看該可執(zhí)行文件依賴什么共享庫:
ldd test
3.4 動態(tài)庫的使用
【使用說明】
- 使用動態(tài)庫生成的可執(zhí)行文件更耻,需將生成的動態(tài)庫也拷貝到目標(biāo)板的連接文件目錄(/lib或/usr/lib)中,才可以順利執(zhí)行麻掸。如果將生成的libfunc.so.1.0.0拷貝到系統(tǒng)lib目錄(如/usr/lib)酥夭,則倒數(shù)第二步就不需要了。
- 使用動態(tài)庫鏈接生成可執(zhí)行文件時脊奋,需要用-L指定動態(tài)庫的位置,如上面指定為當(dāng)前目錄:-L./疙描。
【使用實(shí)例】
$ gcc –fPIC func.c -c –o func.o
$ gcc –shared –o libfunc.so.1.0.0 func.o
$ ln –s libfunc.so.1.0.0 libfunc.so
$ gcc main.c -o main -lfunc -L./
$ export LD_LIBRARY_PATH=$(pwd):$LD_LIBRARY_PATH
$ ./main
四诚隙、程序編譯時和運(yùn)行時庫文件路徑的指定方法
如何指定程序編譯和運(yùn)行時使用的靜態(tài)庫和動態(tài)庫呢?在連接時起胰,可以通過-L選項(xiàng)來指定編譯時所用到的靜態(tài)庫和動態(tài)庫文件路徑久又。在程序運(yùn)行時,可以通過配置/etc/ld.so.conf文件或者使用環(huán)境變量LD_LIBRARY_PATH來配置效五。
4.1 配置/etc/ld.so.conf文件
一般應(yīng)用程序的庫文件不與系統(tǒng)庫文件放在同一目錄下地消,一般把應(yīng)用程序的共享庫文件放在/usr/local/lib下,新建一個屬于自己的目錄apue畏妖,然后把剛才生成的libapue.so復(fù)制過去脉执,同時在/etc/ld.so.conf中添加庫文件的路勁,一行一個戒劫,如:
/usr/local/lib/apue
/usr/X11R6/lib
/opt/lib
也可以在/etc/ld.so.conf.d目錄下創(chuàng)建一個.conf文件半夷,將搜索路徑一行一個的加入該文件中。以后在編譯程序時迅细,加上如下編譯選項(xiàng)巫橄,就可以使用共享庫了。
-L/usr/local/lib/apue –lapue
但需要注意的是:更改/etc/ld.so.conf的設(shè)置方法對于程序連接時的庫(包括共享庫和靜態(tài)庫)的定位已經(jīng)足夠了茵典,但是對于使用了共享庫的程序的執(zhí)行還是不夠的湘换。這是因?yàn)闉榱思涌斐绦驁?zhí)行時對共享庫的定位速度,避免使用搜索路勁查找共享庫的低效率统阿,動態(tài)加載器是直接讀取庫列表文件/etc/ld.so.cache來進(jìn)行搜索的彩倚。Ld.so.cache是一個非文本的數(shù)據(jù)文件,不能直接編輯砂吞,它是根據(jù)/etc/ld.so.conf中設(shè)置的路勁由/sbin/ldconfig命令將這些搜索路徑下的共享庫文件集中在一起而生成的(ldconfig需要root權(quán)限才能執(zhí)行)署恍。
因此,為了保證程序執(zhí)行時對庫的定位蜻直,在/etc/ld.so.conf中進(jìn)行了庫文件搜索路徑設(shè)置后盯质,還需要運(yùn)行/sbin/ldconfig命令更新/etc/ld.so.cache文件袁串。
4.2 修改環(huán)境變量LD_LIBRARY_PATH
方法一需要有root權(quán)限才能設(shè)置,而且當(dāng)系統(tǒng)重新啟動后呼巷,所有的基于GTK2的程序在運(yùn)行時都將使用新安裝的GTK+庫囱修。不幸的是,由于GTK+版本的改變王悍,這有時會給應(yīng)用程序帶來兼容性的問題破镰,造成程序運(yùn)行不正常。為避免出現(xiàn)這種情況压储,可采用在環(huán)境變量LD_LIBRARY_PATH中指明庫的搜索路徑的方法鲜漩,它不需要root權(quán)限,設(shè)置也簡單集惋。
設(shè)置方法如下:
$ export LD_LIBRARY_PATH=/opt/gtk/lib:$LD_LIBRARY_PATH
查看LD_LIBRARY_PATH的內(nèi)容的方法為:
$ echo $LD_LIBRARY_PATH
注意:LD_LIBRARY_PATH的設(shè)定是全局的孕似,過多的使用可能會影響到其他應(yīng)用程序的運(yùn)行,所以多用在調(diào)試中刮刑。
4.3 鏈接時通過-L選型顯示指定路徑
在程序鏈接時喉祭,對于庫文件(靜態(tài)庫和動態(tài)庫)的搜索路徑,可以通過-L參數(shù)顯示指定庫文件路徑雷绢。因?yàn)?L設(shè)置的路徑將被優(yōu)先搜索泛烙,所以在鏈接的時候通常會以這種方式直接指定要鏈接的庫的路徑。如:
$ gcc main.c -o main -lfunc -L./
$ gcc test.c –o test –L./ -lapue
五翘紊、常見問題及解決方法
調(diào)用動態(tài)庫的時候蔽氨,有時明明已經(jīng)將庫的頭文件在目錄通過“-I”include進(jìn)來了,庫所在文件通過“-L”參數(shù)引導(dǎo)霞溪,并指定了“-l”的庫名孵滞,但是通過ldd命令查看是,就是死活找不到指定鏈接的so庫文件鸯匹,這時可通過修改LD_LIBRARY_PATH或者/etc/ld.so.conf文件來指定動態(tài)庫的目錄坊饶。通常這樣做就可以解決庫無法鏈接的問題了。
makefile里面怎么正確的編譯和連接生成的.so庫文件殴蓬,然后又是在其他程序的makefile里面如何編譯和鏈接才能調(diào)用這個庫文件和函數(shù)匿级?
- 通過export命令設(shè)置環(huán)境變量將庫的路徑添加到庫目錄中,這種方法不太方便:
export LD_LIBRARY_PATH=$(LD_LIBRARY_PATH:$(pwd))染厅;
- LD_LIBRARY_PATH可以在/etc/profile痘绎,~/.profile或者./bash_profile里設(shè)置,或者.bashrc里肖粮,改完后運(yùn)行source /etc/profile或./etc/profile孤页;
- 將新的路徑添加進(jìn)/etc/ld.so.conf,然后執(zhí)行/sbin/ldconfig涩馆。