本文主要解決以下幾個問題
1 為什么要使用庫,? 2 庫的分類 3 創(chuàng)建自己的庫 或許大家對自己初學linux時的情形仍記憶尤新吧,。如果沒有一個能較好的解決依賴關系的包管理器,,在linux下安裝軟件將是一件及其痛苦的工作,。你裝a包時,,可能會提示你要先裝b包,當你費盡心力找到b包時,,可能又會提示你要先安裝c包,。我就曾被這樣的事搞的焦頭爛額,至今一提起rpm仍心有余悸,,頭皮發(fā)麻,。說是一朝被蛇咬,十年怕井繩怕也不為過,。 linux下之所以有這許多的依賴關系,,其中一個開發(fā)原則真是功不可沒。這個原則就是:盡量不重復做別人已經(jīng)做過的事,。換句話說就是盡量充分利用別人的勞動成果,。 這就涉及到如何有效的進行代碼復用。 1 為什么要使用庫,? 關于代碼復用的途徑,,一般有兩種。 粘貼復制 這是最沒有技術含量的一種方案,。如果代碼小,,則工作量還可以忍受,如果代碼很龐大,,則此法不可取,。即便有人原意這樣做,但誰又能保證所有的代碼都可得到呢,? 而庫的出現(xiàn)很好的解決了這個問題,。 庫,是一種封裝機制,,簡單說把所有的源代碼編譯成目標代碼后打成的包,。 那么用戶怎么能知道這個庫提供什么樣的接口呢?難道要用nm等工具逐個掃描,? 不用擔心,,庫的開發(fā)者早以把一切都做好了,。除了包含目標代碼的庫外,,一般還會提供一系列的頭文件,,頭文件中就包含了庫的接口。為了讓方便用戶,,再加上一個使用說明就差不多完美了,。 2 庫的分類 2.1 庫的分類 根據(jù)鏈接時期的不同,庫又有靜態(tài)庫和動態(tài)庫之分,。 靜態(tài)庫是在鏈接階段被鏈接的(好像是廢話,,但事實就是這樣),所以生成的可執(zhí)行文件就不受庫的影響了,,即使庫被刪除了,,程序依然可以成功運行。 有別于靜態(tài)庫,,動態(tài)庫的鏈接是在程序執(zhí)行的時候被鏈接的,。所以,即使程序編譯完,,庫仍須保留在系統(tǒng)上,,以供程序運行時調用。(TODO:鏈接動態(tài)庫時鏈接階段到底做了什么) 2.2 靜態(tài)庫和動態(tài)庫的比較 鏈接靜態(tài)庫其實從某種意義上來說也是一種粘貼復制,,只不過它操作的對象是目標代碼而不是源碼而已,。因為靜態(tài)庫被鏈接后庫就直接嵌入可執(zhí)行文件中了,這樣就帶來了兩個問題,。 首先就是系統(tǒng)空間被浪費了,。這是顯而易見的,想象一下,,如果多個程序鏈接了同一個庫,,則每一個生成的可執(zhí)行文件就都會有一個庫的副本,必然會浪費系統(tǒng)空間,。 再者,,人非圣賢,即使是精心調試的庫,,也難免會有錯,。一旦發(fā)現(xiàn)了庫中有bug,挽救起來就比較麻煩了,。必須一一把鏈接該庫的程序找出來,,然后重新編譯。 而動態(tài)庫的出現(xiàn)正彌補了靜態(tài)庫的以上弊端,。因為動態(tài)庫是在程序運行時被鏈接的,,所以磁盤上只須保留一份副本,,因此節(jié)約了磁盤空間。如果發(fā)現(xiàn)了bug或要升級也很簡單,,只要用新的庫把原來的替換掉就行了,。 那么,是不是靜態(tài)庫就一無是處了呢,? 答曰:非也非也,。不是有句話么:存在即是合理。靜態(tài)庫既然沒有湮沒在滔滔的歷史長河中,,就必然有它的用武之地,。想象一下這樣的情況:如果你用libpcap庫編了一個程序,要給被人運行,,而他的系統(tǒng)上沒有裝pcap庫,,該怎么解決呢?最簡單的辦法就是編譯該程序時把所有要鏈接的庫都鏈接它們的靜態(tài)庫,,這樣,,就可以在別人的系統(tǒng)上直接運行該程序了。 所謂有得必有失,,正因為動態(tài)庫在程序運行時被鏈接,,故程序的運行速度和鏈接靜態(tài)庫的版本相比必然會打折扣。然而瑕不掩瑜,,動態(tài)庫的不足相對于它帶來的好處在現(xiàn)今硬件下簡直是微不足道的,,所以鏈接程序在鏈接時一般是優(yōu)先鏈接動態(tài)庫的,除非用-static參數(shù)指定鏈接靜態(tài)庫,。 2.3 如何判斷一個程序有沒有鏈接動態(tài)庫? 答案是用file實用程序,。 file程序是用來判斷文件類型的,在file命令下,,所有文件都會原形畢露的,。 順便說一個技巧。有時在windows下用瀏覽器下載tar.gz或tar.bz2文件,,后綴名會變成奇怪的tar.tar,,到linux有些新手就不知怎么解壓了。但linux下的文件類型并不受文件后綴名的影響,,所以我們可以先用命令file xxx.tar.tar看一下文件類型,,然后用tar加適當?shù)膮?shù)解壓。 另外,,還可以借助程序ldd實用程序來判斷,。 ldd是用來打印目標程序(由命令行參數(shù)指定)所鏈接的所有動態(tài)庫的信息的,如果目標程序沒有鏈接動態(tài)庫,則打印“not a dynamic executable”,,ldd的用法請參考manpage,。 3 創(chuàng)建自己的庫 3.1 創(chuàng)建動態(tài)庫 創(chuàng)建文件hello.c,內容如下: #include void hello(void) { printf("Hello World\n"); } 用命令gcc -shared hello.c -o libhello.so編譯為動態(tài)庫,??梢钥吹剑斍澳夸浵露嗔艘粋€文件libhello.so,。 [leo@leo test]$ file libhello.so libhello.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped 看到了吧,,文件類型是shared object了。 再編輯一個測試文件test.c,,內容如下: int main() { hello(); return 0; } 這下可以編譯了:) [leo@leo test]$ gcc test.c /tmp/ccm7w6Mn.o: In function `main': test.c:(.text+0x1d): undefined reference to `hello' collect2: ld returned 1 exit status 鏈接時gcc找不到hello函數(shù),編譯失敗:(,。原因是hello在我們自己創(chuàng)建的庫中,,如果gcc能找到那才教見鬼呢!ok,,再接再厲,。 [leo@leo test]$ gcc test.c -lhello /usr/lib/gcc/i686-pc-linux-gnu/4.0.0/../../../../i686-pc-linux-gnu/bin/ld: cannot find -lhello collect2: ld returned 1 exit status [leo@leo test]$ gcc test.c -lhello -L. [leo@leo test]$ 第一次編譯直接編譯,gcc默認會鏈接標準c庫,,但符號名hello解析不出來,,故連接階段通不過了。 現(xiàn)在用gcc test.c -lhello -L.已經(jīng)編譯成功了,,默認輸出為a.out?,F(xiàn)在來試著運行一下: [leo@leo test]$ ./a.out ./a.out: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory 咦,怎么回事,?原來雖然鏈接時鏈接器(dynamic linker)找到了動態(tài)庫libhello.so,,但動態(tài)加載器(dynamic loader, 一般是/lib/ld-linux.so.2)卻沒找到,。再來看看ldd的輸出: [leo@leo test]$ ldd a.out linux-gate.so.1 => (0xffffe000) libhello.so => not found libc.so.6 => /lib/libc.so.6 (0x40034000) /lib/ld-linux.so.2 (0x40000000) 果然如此,,看到?jīng)]有,libhello.so => not found,。 linux為我們提供了兩種解決方法: 1.可以把當前路徑加入/etc/ld.so.conf中然后運行l(wèi)dconfig,,或者以當前路徑為參數(shù)運行l(wèi)dconfig(要有root權限才行)。 2.把當前路徑加入環(huán)境變量LD_LIBRARY_PATH中 當然,,如果你覺得不會引起混亂的話,,可以直接把該庫拷入/lib,/usr/lib/等位置(無可避免,這樣做也要有權限),,這樣鏈接器和加載器就都可以準確的找到該庫了,。 我們采用第二種方法: [leo@leo test]$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH [leo@leo test]$ ldd a.out linux-gate.so.1 => (0xffffe000) libhello.so => ./libhello.so (0x4001f000) libc.so.6 => /lib/libc.so.6 (0x40036000) /lib/ld-linux.so.2 (0x40000000) 哈哈,這下ld-linux.so.2就可以找到libhello.so這個庫了。 現(xiàn)在可以直接運行了: [leo@leo test]$ ./a.out Hello World 3.2 創(chuàng)建靜態(tài)庫 仍使用剛才的hello.c和test.c,。 第一步,,生成目標文件。 [leo@leo test]$ gcc -c hello.c [leo@leo test]$ ls hello.o -l -rw-r--r-- 1 leo users 840 5月 6 12:48 hello.o 第二步,把目標文件歸檔,。 [leo@leo test]$ ar r libhello.a hello.o ar: creating libhello.a OK,libhello.a就是我們所創(chuàng)建的靜態(tài)庫了,,簡單吧:) [leo@leo test]$ file libhello.a libhello.a: current ar archive 下面一行命令就是教你如何在程序中鏈接靜態(tài)庫的: [leo@leo test]$ gcc test.c -lhello -L. -static -o hello.static 我們來用file命令比較一下用動態(tài)庫和靜態(tài)庫鏈接的程序的區(qū)別: [leo@leo test]$ gcc test.c -lhello -L. -o hello.dynamic 正如前面所說,鏈接器默認會鏈接動態(tài)庫(這里是libhello.so),,所以只要把上個命令中的-static參數(shù)去掉就可以了,。 用file實用程序驗證一下是否按我們的要求生成了可執(zhí)行文件: [leo@leo test]$ file hello.static hello.dynamic hello.static: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.6, statically linked, not stripped hello.dynamic: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.6, dynamically linked (uses shared libs), not stripped 不妨順便練習一下ldd的用法: [leo@leo test]$ ldd hello.static hello.dynamic hello.static: not a dynamic executable hello.dynamic: linux-gate.so.1 => (0xffffe000) libhello.so => ./libhello.so (0x4001f000) libc.so.6 => /lib/libc.so.6 (0x40034000) /lib/ld-linux.so.2 (0x40000000) OK,看來沒有問題,,那就比較一下大小先: [leo@leo test]$ ls -l hello.[ds]* -rwxr-xr-x 1 leo users 5911 5月 6 12:54 hello.dynamic -rwxr-xr-x 1 leo users 628182 5月 6 12:54 hello.static 看到區(qū)別了吧,,鏈接靜態(tài)庫的目標程序和鏈接動態(tài)庫的程序比起來簡直就是一個龐然大物! 這么小的程序,,很難看出執(zhí)行時間的差別,,不過為了完整起見,還是看一下time的輸出吧: [leo@leo test]$ time ./hello.static Hello World real 0m0.001s user 0m0.000s sys 0m0.001s [leo@leo test]$ time ./hello.dynamic Hello World real 0m0.001s user 0m0.000s sys 0m0.001s 如果程序比較大的話,,應該效果會很明顯的,。 |
|