受歡迎的廣州做網(wǎng)站搜狗搜索推廣
?
平時我們寫程序都必須?include?很多頭文件,因為可以避免重復(fù)造輪子,軟件大廈可不是單靠一個人就能完成的。但是你是否知道引用的那些頭文件中的函數(shù)是怎么被執(zhí)行的呢?這就要牽扯到鏈接庫了!
庫有兩種,一種是?靜態(tài)鏈接庫,一種是?動態(tài)鏈接庫,不管是哪一種庫,要使用它們,都要在程序中包含相應(yīng)的?include?頭文件。我們先來回顧一下程序編譯的過程。如下圖:
我們結(jié)合gcc指令來看一下每個階段生成的文件:
1 |
|
生成一個helloWorld.o文件,該文件是將源文件編譯成的匯編文件,在鏈接之前,該文件不是可執(zhí)行文件。而
1 |
|
生成的是一個helloWorld的執(zhí)行文件,格式為ELF(與windows不一樣)。該文件為鏈接后的可執(zhí)行文件。
1、靜態(tài)鏈接庫
什么是靜態(tài)鏈接呢?即在鏈接階段,將源文件中用到的庫函數(shù)與匯編生成的目標(biāo)文件.o合并生成可執(zhí)行文件。該可執(zhí)行文件可能會比較大。這種鏈接方式的好處是:方便程序移植,因為可執(zhí)行程序與庫函數(shù)再無關(guān)系,放在如何環(huán)境當(dāng)中都可以執(zhí)行。
缺點是:文件太大,一個全靜態(tài)方式生成的簡單print文件都有857K。而動態(tài)鏈接生成的一樣的可執(zhí)行文件卻只要8.4K。
文件內(nèi)容很簡單,就是一個printf("hello world!\n");
因為包含庫文件stdio,所以靜態(tài)編譯出的文件很大。如果你想嘗試的話,可以這樣編譯:
1 |
|
在linux中,靜態(tài)庫為lib*.a,動態(tài)庫為lib*.so。
下面我們來寫一個庫文件,然后生成一個靜態(tài)庫,然后嘗試著調(diào)用一下它。一個簡單的add函數(shù),頭文件為
頭文件對應(yīng)的源文件:
下面我們來生成靜態(tài)庫:
輸入:g++ -c add.cpp 生成.o目標(biāo)文件
然后用ar命令進一步生成庫libadd.a:
1 |
|
這樣就生成了一個靜態(tài)鏈接庫libadd.a。
下面我們來寫一個測試文件:
1 2 3 4 5 6 7 8 9 10 11 |
?
|
因為我的目錄結(jié)構(gòu)是add.cpp, addlib(文件夾),在addlib中是頭文件和靜態(tài)庫,所以include用相對路徑找到頭文件add.h。
下面我們編譯一下該文件:
1 |
|
-L是指定加載庫文件的路徑
-l是指定加載的庫文件。
運行一下:
可見調(diào)用成功。
2、動態(tài)鏈接庫
我們知道靜態(tài)鏈接的話,文件會很大,往往實現(xiàn)很小的一個功能就需要占用很大的空間,而且每次庫文件升級的話,都要重新編譯源文件,很不方便。具體下面如下:
對于靜態(tài)編譯的程序1和程序2,都應(yīng)用庫staticMath。在內(nèi)存中就又兩份相同的staticMath目標(biāo)文件,很浪費空間,一旦程序數(shù)量過多就很可能會內(nèi)存不足。
這么大的內(nèi)存才只能運行這幾個程序,實在不甘心。
這樣就又了動態(tài)庫發(fā)揮威力的地方了。我們來看看動態(tài)鏈接的結(jié)果:
我們看到在這種模型中,兩個程序只應(yīng)用一個庫,這個目標(biāo)文件在內(nèi)存中只有一份,供所有程序使用。
并且在程序運行過程中動態(tài)調(diào)用庫文件,很方便,又不占空間,但是動態(tài)鏈接有一個缺點就是可移植性太差,如果兩臺電腦運行環(huán)境不同,動態(tài)庫存放的位置不一樣,很可能導(dǎo)致程序運行失敗。
在具體的應(yīng)用中,靜態(tài)與動態(tài)應(yīng)當(dāng)合理選擇!!!
下面我們來生成一個動態(tài)庫,輸入:
1 |
|
這樣就生成了一個libadd.so的動態(tài)庫。
下面我們用動態(tài)鏈接的方式編譯test.cpp,輸入:
1 |
|
該命令和剛剛靜態(tài)鏈接一樣。注意-l后面接的是lib與so中間的庫名稱。
我們執(zhí)行一下:
發(fā)現(xiàn)不行,因為執(zhí)行程序找不到libadd.so。
可以看到test執(zhí)行程序用到的?libadd.so?沒有找到。。。
原因是在?/etc/ld.so.conf?文件中設(shè)置了動態(tài)鏈接庫了尋找路徑。
可以看到有很多路徑設(shè)置文件,在?ld.so.conf.d?中,我們在下面添加一下我們?libadd.so?的路徑。
然后再執(zhí)行一下?ldconfig?命令。
這下就可以成功執(zhí)行test文件了。
注意一下,有人說為什么我程序中?extern int number;可以直接編譯不需要什么靜態(tài)鏈接庫,動態(tài)鏈接庫。那是因為你在鏈接時已經(jīng)將number變量定義的目標(biāo)文件.o和源文件進行了鏈接,如:gcc -o main main.o test.o。如果你只是單純的用?main.o?進行鏈接,是生成不了可執(zhí)行目標(biāo)文件的,如:gcc -o main main.c會報告未定義的number引用。
?
綜上說述,靜態(tài)和動態(tài)鏈接庫的選擇要視情況而定。一般比較推薦動態(tài)鏈接方式,因為可以很好的節(jié)約內(nèi)存,而且方便以后的庫文件升級。
?
g++(gcc)編譯選項
- -shared?:指定生成動態(tài)鏈接庫。
- -static?:指定生成靜態(tài)鏈接庫。
- -fPIC?:表示編譯為位置獨立的代碼,用于編譯共享庫。目標(biāo)文件需要創(chuàng)建成位置無關(guān)碼,念上就是在可執(zhí)行程序裝載它們的時候,它們可以放在可執(zhí)行程序的內(nèi)存里的任何地方。
- -L.?:表示要連接的庫所在的目錄。
- -l:指定鏈接時需要的動態(tài)庫。編譯器查找動態(tài)連接庫時有隱含的命名規(guī)則,即在給出的名字前面加上lib,后面加上.a/.so來確定庫的名稱。
- -Wall?:生成所有警告信息。
- -ggdb?:此選項將盡可能的生成gdb的可以使用的調(diào)試信息。
- -g?:編譯器在編譯的時候產(chǎn)生調(diào)試信息。
- -c?:只激活預(yù)處理、編譯和匯編,也就是把程序做成目標(biāo)文件(.o文件)。
- -Wl,options?:把參數(shù)(options)傳遞給鏈接器ld。如果options中間有逗號,就將options分成多個選項,然后傳遞給鏈接程序。