轉載地址 http://blog.csdn.net/high_high/article/details/7193264
先說說庫文件是怎么來的吧。
以C為例,我們寫一個程序,一般都不會把所有的函數都寫在一個文件里面,通常都是划分模塊,然后一個模塊若干個文件,然后在main文件里面調用這些模塊。我這里用一個magic.c文件代替實際程序中的所有的文件,你就當這個magic.c文件非常神奇,你調用里面的magic()函數后,它會自動按你的想法把剩下的工作都完成。下面是兩個文件:
mian.c
- int main(){
- magic();
- }
magic.c:
- #include <stdio.h>
- void magic(){
- printf("This is a magic function\n");
- }
main.c文件里面沒有包括任何頭文件,因為我們的編譯是手動按步驟的,實際編碼不推薦這樣做,這里可以更加清楚的知道頭文件的作用。
一般從C源文件到可執行代碼要經過以下4個步驟:預編譯(preprocess gcc -E,生成.i文件),編譯(compile gcc -S,生成.s文件),匯編(assemble gcc -c,生成.o文件),鏈接(link 生成可執行文件)。這里只討論最后兩個步驟,匯編和鏈接。
匯編后的結果是每個源文件都有了對應的二進制代碼;鏈接是把所有的二進制代碼打包成一個文件,最后得到可執行文件。
使用這個命令匯編main.c:gcc -Wall -c main.c
-Wall 是列出警告的開關,如果沒有這個開關,匯編成功,什么提示都沒有,如果打開這個開關,會得到如下一個警告:
main.c:2:3: warning: implicit declaration of function ‘magic’ [-Wimplicit-function-declaration]
要消除這個警告很容易,有兩個辦法:
1)前面加個聲明就好了,void magic();
2)寫個magic.h的頭文件:
- void magic();
然后在main.c里面包含進來:#include "magic.h"。
這兩種辦法的作用是告訴main,現在沒有magic的實現不要緊,我確定一定以及肯定這個magic函數是存在的,放心使用就好了,而且告訴了main這個magic該怎么用,參數是什么,返回值是什么。
但是沒有magic的聲明也沒有問題,只是警告,不是錯誤,因為我們知道我們的magic是怎么定義的,而且我們也確定會在后面鏈接magic函數。
但是問題就出在這里了,比如我們寫了一個超級牛逼的函數想讓別人使用,但是這個超級牛逼的函數要下個禮拜才能給別人怎么辦呢?我們可以先給個頭文件,然后拍着胸脯說你就按照我這個函數聲明寫,只要你的調用(call)沒問題,程序運行的結果就沒問題。
一個禮拜后到了可以給別人我們的超級牛逼函數的時候了,這時候我們也有兩種選擇可以選。
第一,發源文件,這個沒話說了,很棒,互相學習進步,讓別人自己把源文件匯編成二進制代碼,然后再和他的main代碼鏈接成可執行代碼就好了。
第二,如果涉及商業或者版權因素,那么我們就只能自己把匯編后的二進制代碼給別人,然后讓別人再去鏈接。這里又體現了頭文件的好處,只需要知道函數怎么調用就可以了,不用去知道函數內部怎么寫的。比如剛開始學C只需要知道怎么使用 fopen, fclose, fprintf, fscanf 等等就好了,等以后慢慢深入再去探個究竟。
好了,那么庫文件究竟是什么呢?其實就是匯編后的二進制代碼,是別人寫好的超級牛逼的代碼匯編以后放在那里讓我們使用的。但是這個匯編后的二進制代碼和我們自己生成的還有些不同,具體來說有兩種。
庫文件本身分為兩種:靜態庫文件(static library)和動態庫文件(dynamic library),linux下,靜態庫文件以.a結尾(archive),動態庫文件以.so結尾(shared object)。
靜態庫文件其實就是匯編后的二進制代碼的一個壓縮文件(archive),里面可以有一個或者N個.o文件。比如上面的magic.c,可以這樣生成靜態庫文件:
首先匯編:gcc -c magic.c ,生成magic.o,然后使用壓縮文件命令(ar)生成靜態庫文件:ar rc libmagic.a magic.o ,其中rc是壓縮選項,libmagic.a是給靜態庫文件起的名字,magic.o 就是用來生成靜態庫文件的二進制文件,當然后面可以接很多個.o文件,把他們壓縮成一個靜態庫文件給別人使用。
libmagic.a 弄好以后就可以和main的二進制文件鏈接在一起了: gcc -o main main.o -lmagic -L.
-o main 選項是把產生的可執行文件命名為main,沒有的話默認名字是a.out,-lmagic表示使用庫文件libmagic.so(后面介紹)或者libmagic.a,如果都存在的話使用.so文件。現在還沒有libmagic.so文件,所以鏈接了libmagic.a里面的magic函數。
動態庫文件有些不同,直接使用gcc生成,首先還是匯編:gcc -fPIC -c magic.c ,因為匯編后的二進制代碼要拿去做動態庫,所以多了一個-fPIC選項,用來確定庫中函數的鏈接位置。然后從二進制文件生成動態庫文件:gcc -shared -o libmagic.so magic.o。后面可以接很多.o文件。
動態庫的鏈接的命令和靜態庫一樣,但是做的事情不一樣。鏈接靜態庫文件是把庫里面的函數復制了到了可執行文件里面,所以可執行文件生成以后有沒有靜態庫文件就不重要了。而動態鏈接只是在可執行文件里面記錄了那個函數需要使用的動態庫文件,真正的鏈接是在運行(run)的時候,只有運行我們生成的可執行文件,到了要使用動態庫文件里面函數的時候,那個函數才會被加載到內存中再執行。所以涉及到了操作系統尋找動態鏈接庫的路徑問題。可以使用ldd程序查看我們的程序都用到了哪些動態庫文件: ldd main,輸出如下:
linux-vdso.so.1 => (0x00007fff8c9dd000)
libmagic.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb0f846c000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb0f8829000)
可以看到libmagic.so沒有找到,所以如果運行main也會報錯:./main: error while loading shared libraries: libmagic.so: cannot open shared object file: No such file or directory 。要讓系統找到動態庫文件就必須設置動態庫文件的路徑了(不同於可執行文件路徑PATH,頭文件路徑INCLUDE 哦),如果要在動態庫文件的搜尋路徑里面加上當前路徑,可以如下設置:
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. /* 當前路徑是. */
export LD_LIBRARY_PATH
設置好路徑以后動態庫文件就能被找到了,再運行ldd main,輸出下面的結果:
linux-vdso.so.1 => (0x00007fffb55ff000)
libmagic.so (0x00007ff515fa7000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff515bec000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff5161ab000)
關於鏈接再多說一句吧,gcc的鏈接是調用了另一個程序:ld。可以自己手動調用ld或者寫在Makefile里面,不過一般不用那么麻煩,用gcc就好了。
由於鏈接方法不用,所以鏈接靜態庫文件后的可執行文件一般都比較大,比如上面的例子,鏈接靜態庫文件后的main大小是8498個字節,而鏈接動態庫文件后的main的大小是8401字節。好像文件大小沒少多少,那是因為我們的magic函數小,大一點區別就很明顯了。
謝謝觀賞,歡迎點評。
參考文獻:
The art of debugging with gdb,ddd, and eclipse. Norman Matloff and Peter Jay Salzman.
附錄 :https://www.cnblogs.com/yangg518/p/5842651.html
一、基本概念
1.1什么是庫
在windows平台和linux平台下都大量存在着庫。
本質上來說庫是一種可執行代碼的二進制形式,可以被操作系統載入內存執行。
由於windows和linux的平台不同(主要是編譯器、匯編器和連接器的不同),因此二者庫的二進制是不兼容的。
本文僅限於介紹linux下的庫。
1.2庫的種類
linux下的庫有兩種:靜態庫和共享庫(動態庫)。
二者的不同點在於代碼被載入的時刻不同。
靜態庫的代碼在編譯過程中已經被載入可執行程序,因此體積較大。
共享庫的代碼是在可執行程序運行時才載入內存的,在編譯過程中僅簡單的引用,因此代碼體積較小。
1.3庫存在的意義
庫是別人寫好的現有的,成熟的,可以復用的代碼,你可以使用但要記得遵守許可協議。
現實中每個程序都要依賴很多基礎的底層庫,不可能每個人的代碼都從零開始,因此庫的存在意義非同尋常。
共享庫的好處是,不同的應用程序如果調用相同的庫,那么在內存里只需要有一份該共享庫的實例。
1.4庫文件是如何產生的在linux下
靜態庫的后綴是.a,它的產生分兩步
Step 1.由源文件編譯生成一堆.o,每個.o里都包含這個編譯單元的符號表
Step 2.ar命令將很多.o轉換成.a,成為靜態庫
動態庫的后綴是.so,它由gcc加特定參數編譯產生。
具體方法參見后文實例。
1.5庫文件是如何命名的,有沒有什么規范
在linux下,庫文件一般放在/usr/lib和/lib下,
靜態庫的名字一般為libxxxx.a,其中xxxx是該lib的名稱
動態庫的名字一般為libxxxx.so.major.minor,xxxx是該lib的名稱,major是主版本號, minor是副版本號
1.6如何知道一個可執行程序依賴哪些庫
ldd命令可以查看一個可執行程序依賴的共享庫,
例如# ldd /bin/lnlibc.so.6
=> /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2
=> /lib/ld- linux.so.2 (0×40000000)
可以看到ln命令依賴於libc庫和ld-linux庫
1.7可執行程序在執行的時候如何定位共享庫文件
當系統加載可執行代碼時候,能夠知道其所依賴的庫的名字,但是還需要知道絕對路徑。
此時就需要系統動態載入器(dynamic linker/loader)
對於elf格式的可執行程序,是由ld-linux.so*來完成的,它先后搜索elf文件的 DT_RPATH段—環境變量LD_LIBRARY_PATH—/etc/ld.so.cache文件列表—/lib/,/usr/lib目錄找到庫文件后將其載入內存
如:export LD_LIBRARY_PATH=’pwd’
將當前文件目錄添加為共享目錄
1.8在新安裝一個庫之后如何讓系統能夠找到他
如果安裝在/lib或者/usr/lib下,那么ld默認能夠找到,無需其他操作。
如果安裝在其他目錄,需要將其添加到/etc/ld.so.cache文件中,步驟如下
1.編輯/etc/ld.so.conf文件,加入庫文件所在目錄的路徑
2.運行ldconfig,該命令會重建/etc/ld.so.cache文件