linux使用gcc編譯c語(yǔ)言共享庫(kù)步驟
對(duì)任何程序員來(lái)說(shuō)庫(kù)都是必不可少的。所謂的庫(kù)是指已經(jīng)編譯好的供你使用的代碼。它們常常提供一些通用功能,例如鏈表和二叉樹(shù)可以用來(lái)保存任何數(shù)據(jù),或者是一個(gè)特定的功能例如一個(gè)數(shù)據(jù)庫(kù)服務(wù)器的接口,就像MySQL。
大部分大型的軟件項(xiàng)目都會(huì)包含若干組件,其中一些你發(fā)現(xiàn)可以用在其他項(xiàng)目中,又或者你僅僅出于組織目的將不同組件分離出來(lái)。當(dāng)你有一套可復(fù)用的并且邏輯清晰的函數(shù)時(shí),將其構(gòu)建為一個(gè)庫(kù)會(huì)十分有用,這樣你就不將這些源代碼拷貝到你的源代碼中,而且每次都要再次編譯它們。除此之外,你還可以保證你的程序各模塊隔離,這樣你修改其中一個(gè)模塊時(shí)也不會(huì)影響到其他的模塊。一旦你寫好一個(gè)模塊并且通過(guò)測(cè)試,你就可以無(wú)限次地安全地復(fù)用它,這可以節(jié)省大量時(shí)間和麻煩。
構(gòu)建靜態(tài)庫(kù)太簡(jiǎn)單了,對(duì)此我們幾乎不會(huì)遇到什么問(wèn)題。我不想說(shuō)明如何構(gòu)建靜態(tài)庫(kù)。在此我只討論共享庫(kù),因?yàn)閷?duì)大多數(shù)人來(lái)說(shuō)它更加難懂。
在我們正式開(kāi)始前,讓我們列一下綱要來(lái)了解從源代碼到運(yùn)行程序之間發(fā)生了什么:
預(yù)處理:這個(gè)階段處理所有預(yù)處理指令。基本上就是源代碼中所有以#開(kāi)始的行,例如#define和#include。
編譯:一旦源文件預(yù)處理完畢,接下來(lái)就是編譯。因?yàn)樵S多人提到編譯時(shí)都是指整個(gè)程序構(gòu)建過(guò)程,因此本步驟也稱作“compilation proper”。本步驟將.c文件轉(zhuǎn)換為.o文件。
連接:到這一步就該將你所有的對(duì)象文件和庫(kù)串聯(lián)起來(lái)使之成為最后的可運(yùn)行程序。需要注意的是,靜態(tài)庫(kù)實(shí)際上已經(jīng)植入到你的程序中,而共享庫(kù),只是在程序中包含了對(duì)它們的引用?,F(xiàn)在你有了一個(gè)完整的程序,隨時(shí)可以運(yùn)行。當(dāng)你從shell中啟動(dòng)它,它就被傳遞給了加載器。
加載:本步驟發(fā)生在你的程序啟動(dòng)時(shí)。首先程序需要被掃描以便引用共享庫(kù)。程序中所有被發(fā)現(xiàn)的引用都立即生效,對(duì)應(yīng)的庫(kù)也被映射到程序。
第3步和第4步就是共享庫(kù)的奧秘所在。
現(xiàn)在,開(kāi)始我們一個(gè)簡(jiǎn)單的示例。
foo.h:
#ifndef foo_h__
#define foo_h__
extern void foo(void);
#endif // foo_h__
foo.c:
#include <stdio.h>
void foo(void)
{
puts("Hello, I'm a shared library");
}
main.c:
#include <stdio.h>
#include "foo.h"
int main(void)
{
puts("This is a shared library test...");
foo();
return 0;
}
oo.h 定義了一個(gè)接口連接我們的庫(kù),一個(gè)簡(jiǎn)單的函數(shù),foo()。foo.c包含了這個(gè)函數(shù)的實(shí)現(xiàn),main.c是一個(gè)用到我們庫(kù)的驅(qū)動(dòng)程序。
為了更好的演示本例子,所有代碼都放在/home/username/foo目錄下。
Step 1: 編譯無(wú)約束位代碼
我們需要把我們庫(kù)的源文件編譯成無(wú)約束位代碼。無(wú)約束位代碼是存儲(chǔ)在主內(nèi)存中的機(jī)器碼,執(zhí)行的時(shí)候與絕對(duì)地址無(wú)關(guān)。
$ gcc -c -Wall -Werror -fpic foo.c
Step 2: 從一個(gè)對(duì)象文件創(chuàng)建共享庫(kù)
現(xiàn)在讓我們將對(duì)象文件變成共享庫(kù)。我們將其命名為libfoo.so:
$ gcc -shared -o libfoo.so foo.o
Step 3: 連接共享庫(kù)
如你所見(jiàn),一切都很簡(jiǎn)單。我們現(xiàn)在有了一個(gè)共享庫(kù)。現(xiàn)在我們編譯我們的main.c并且將它連接到libfoo。我們將最終的運(yùn)行程序命名為test。注意:-lfoo選項(xiàng)并不是搜尋foo.o,而是libfoo.so。GCC編譯器會(huì)假定所有的庫(kù)都是以lib開(kāi)頭,以.so或.a結(jié)尾(.so是指shared object共享對(duì)象或者shared libraries共享庫(kù),.a是指archive檔案,或者靜態(tài)連接庫(kù))。
$ gcc -Wall -o test main.c -lfoo
/usr/bin/ld: cannot find -lfoo
collect2: ld returned 1 exit status
告訴GCC去哪找共享庫(kù)
Uh-oh!連接器不知道該去哪里找到libfoo。GCC有一個(gè)默認(rèn)的搜索列表,但我們的目錄并不在那個(gè)列表當(dāng)中。我們需要告訴GCC去哪里找到libfoo.so。這就要用到-L選項(xiàng)。在本例中,我們將使用當(dāng)前目錄/home/username/foo:
$ gcc -L/home/username/foo -Wall -o test main.c -lfoo
Step 4: 運(yùn)行時(shí)使用庫(kù)
好的,沒(méi)有異常。讓我們運(yùn)行一下程序:
$ ./test
./test: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory
Oh no! 加載器不能找到共享庫(kù)。我們沒(méi)有將它安裝到標(biāo)準(zhǔn)位置,因此我們需要幫一幫加載器。我們有兩個(gè)選項(xiàng):使用環(huán)境變量LD_LIBRARY_PATH或者rpath。讓我們先看看LD_LIBRARY_PATH:
使用LD_LIBRARY_PATH
$ echo $LD_LIBRARY_PATH
目前什么都沒(méi)有?,F(xiàn)在把我們的工作目錄添加到LD_LIBRARY_PATH中:
$ LD_LIBRARY_PATH=/home/username/foo:$LD_LIBRARY_PATH
$ ./test
./test: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory
為什么還報(bào)錯(cuò)?雖然我們的目錄在LD_LIBRARY_PATH,但是我們還沒(méi)有導(dǎo)出它。在Linux中,如果你不將修改導(dǎo)出到一個(gè)環(huán)境變量,這些修改是不會(huì)被子進(jìn)程繼承的。加載器和我們的測(cè)試程序沒(méi)有繼承我們所做的修改,不過(guò)放心,要修復(fù)這個(gè)問(wèn)題很簡(jiǎn)單:
$ export LD_LIBRARY_PATH=/home/username/foo:$LD_LIBRARY_PATH
$ ./test
This is a shared library test...
Hello, I'm a shared library
很好,運(yùn)行正常!LD_LIBRARY_PATH很適合做快速測(cè)試,尤其是那些你沒(méi)有管理員權(quán)限的系統(tǒng)。另一方面,導(dǎo)出LD_LIBRARY_PATH變量意味著可能會(huì)造成其他依賴LD_LIBRARY_PATH的程序出現(xiàn)問(wèn)題,因此在做完測(cè)試后最好將LD_LIBRARY_PATH恢復(fù)成之前的樣子。
使用rpath
現(xiàn)在讓我們來(lái)試試rpath,首先需要清除LD_LIBRARY_PATH,確保我們是使用rpath來(lái)搜索庫(kù)文件。Rpath,或者稱為run path,是種可以將共享庫(kù)位置嵌入程序中的方法,從而不用依賴于默認(rèn)位置和環(huán)境變量。我們?cè)谶B接環(huán)節(jié)使用rpath。注意“-Wl,-rpath=/home/username/foo”選項(xiàng)。-Wl會(huì)發(fā)送以逗號(hào)分隔的選項(xiàng)到連接器,因此我們通過(guò)它發(fā)送-rpath選項(xiàng)到連接器。(譯者按:逗號(hào)分隔符后面沒(méi)有空格,而是緊跟需要發(fā)送的選項(xiàng)。本例中為-rpath。一定注意"-Wl,-rpath"之間沒(méi)有空格。)
$ unset LD_LIBRARY_PATH
$ gcc -L/home/username/foo -Wl,-rpath=/home/username/foo -Wall -o test main.c -lfoo
$ ./test
This is a shared library test...
Hello, I'm a shared library
非常好,奏效了。rpath方法非常棒,因?yàn)槊總€(gè)程序都可以單獨(dú)羅列它自己的共享庫(kù)位置,因此不同的程序不會(huì)再在錯(cuò)誤的路徑上搜索LD_LIBRARY_PATH。
rpath和LD_LIBRARY_PATH
rpath也存在一些反作用面。首先,它要求共享庫(kù)必須安裝在一個(gè)固定的位置,這樣所有的用戶才可以在同一個(gè)位置訪問(wèn)到庫(kù)。這就意味著在系統(tǒng)配置中不夠靈活。其次,如果庫(kù)涉及NFS掛載或者其他網(wǎng)絡(luò)驅(qū)動(dòng),你在啟動(dòng)程序時(shí)會(huì)遇到延時(shí)或者更糟的情況。
使用ldconfig修改ld.so
如果我們想讓系統(tǒng)上所有用戶都可以使用我的庫(kù)時(shí)該怎么辦?對(duì)此,你需要管理員權(quán)限。緣由有二:首先,將庫(kù)放到標(biāo)準(zhǔn)位置,很可能是/usr/lib或者/usr/local/lib,這些地方普通用戶是沒(méi)有寫的權(quán)限。其次,你需要修改ld.so配置文件并緩存。以root身份做一下操作:
$ cp /home/username/foo/libfoo.so /usr/lib
$ chmod 0755 /usr/lib/libfoo.so
現(xiàn)在文件在標(biāo)準(zhǔn)位置,對(duì)所有人都可讀。我們現(xiàn)在需要告訴加載器庫(kù)文件可用,因此讓我們更新一下緩存:
$ ldconfig
這將創(chuàng)建一個(gè)鏈接到我們的共享庫(kù),并且更新緩存以便它可立即生效。讓我們?cè)俸藢?shí)一下:
$ ldconfig -p | grep foo
libfoo.so (libc6) => /usr/lib/libfoo.so
現(xiàn)在我們的庫(kù)安裝好了,在我們開(kāi)始測(cè)試它之前,我們一定要先清理一下其他東西:
以防萬(wàn)一,先清理一下LD_LIBRARY_PATH:
$ unset LD_LIBRARY_PATH
重新連接我們的可執(zhí)行程序。注意:我們不需要-L選項(xiàng),因?yàn)槲覀兊膸?kù)保存在默認(rèn)位置,我們可以不用rpath選項(xiàng):
$ gcc -Wall -o test main.c -lfoo
讓我們確認(rèn)一下我們將使用/usr/lib中我們庫(kù)的實(shí)例,使用ldd命令:
$ ldd test | grep foo
libfoo.so => /usr/lib/libfoo.so (0x00a42000)
很好,現(xiàn)在運(yùn)行一下程序吧:
$ ./test
This is a shared library test...
Hello, I'm a shared library
以上就是所有內(nèi)容。我們講述了如何構(gòu)建一個(gè)共享庫(kù),如何連接,如果解決最常見(jiàn)的共享庫(kù)加載問(wèn)題,還有各種方法的優(yōu)劣性。
附:
1. Shared Libraries(共享庫(kù)) 和 Static Libraries(靜態(tài)庫(kù))區(qū)別
共享庫(kù)是以.so(Windows平臺(tái)為.dll,OS X平臺(tái)為.dylib)作為后綴的文件。所有和庫(kù)有關(guān)的代碼都在這一個(gè)文件中,程序在運(yùn)行時(shí)引用它。使用共享庫(kù)的程序只會(huì)引用共享庫(kù)中它要用到的那段代碼。
靜態(tài)庫(kù)是以.a(Windows平臺(tái)為.lib)作為后綴的文件。所有和庫(kù)有關(guān)的代碼都在這一個(gè)文件中,靜態(tài)庫(kù)在編譯時(shí)就被直接鏈接到了程序中。使用靜態(tài)庫(kù)的程序從靜態(tài)庫(kù)拷貝它要使用的代碼到自身當(dāng)中。(Windows還有一種.lib文件是用來(lái)引用.dll文件,但其實(shí)它們和第一種情況是一樣的。)
兩種庫(kù)各有千秋。
使用共享庫(kù)可以減少程序中重復(fù)代碼的數(shù)量,讓程序體積更小。而且讓你可以用一個(gè)功能相同的對(duì)象來(lái)替換共享對(duì)象,這樣可以在增加性能的同時(shí)不用重新編譯那些使用到該庫(kù)的程序。但是使用共享庫(kù)會(huì)小額增加函數(shù)的執(zhí)行的成本,同樣還會(huì)增加運(yùn)行時(shí)的加載成本,因?yàn)楣蚕韼?kù)中的符號(hào)需要關(guān)聯(lián)到它們使用的東西上。共享庫(kù)可以在運(yùn)行時(shí)加載到程序中,這是二進(jìn)制插件系統(tǒng)最通用的一種實(shí)現(xiàn)機(jī)制。
靜態(tài)庫(kù)總體上增加了程序體積,但它也意味著你無(wú)需隨時(shí)隨地都攜帶一份要用到的庫(kù)的拷貝。因?yàn)榇a在編譯時(shí)就已經(jīng)被關(guān)聯(lián)在一起,因此在運(yùn)行時(shí)沒(méi)有額外的消耗。
2. GCC首先在/usr/local/lib搜索庫(kù)文件,其次在/usr/lib,然后搜索-L參數(shù)指定路徑,搜索順序和-L參數(shù)給出路徑的順序一致。
3. 默認(rèn)的GNU加載器ld.so,按以下順序搜索庫(kù)文件:
首先搜索程序中DT_RPATH區(qū)域,除非還有DT_RUNPATH區(qū)域。
其次搜索LD_LIBRARY_PATH。如果程序是setuid/setgid,出于安全考慮會(huì)跳過(guò)這步。
搜索DT_RUNPATH區(qū)域,除非程序是setuid/setgid。
搜索緩存文件/etc/ld/so/cache(停用該步請(qǐng)使用'-z nodeflib'加載器參數(shù))
搜索默認(rèn)目錄/lib,然后/usr/lib(停用該步請(qǐng)使用'-z nodeflib'加載器參數(shù))
- C/C++編譯器GCC下的常用編譯命令總結(jié)
- 解決gcc編譯報(bào)錯(cuò)unknown type name ‘bool‘問(wèn)題
- 使用MinGW使Windows通過(guò)gcc實(shí)現(xiàn)C或C++程序本地編譯執(zhí)行的方法
- GCC 編譯c程序的方法及過(guò)程解析
- GCC 編譯使用動(dòng)態(tài)鏈接庫(kù)和靜態(tài)鏈接庫(kù)的方法
- Linux上安裝GCC編譯器過(guò)程
- Linux系統(tǒng)下gcc命令使用詳解
- Linux gcc命令的具體使用
- GCC編譯過(guò)程(預(yù)處理,編譯,匯編,鏈接)及GCC命令詳解
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)圖的鄰接矩陣存儲(chǔ)操作
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)圖的鄰接矩陣存儲(chǔ)操作,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08詳解C/C++高精度算法的簡(jiǎn)單實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了C/C++中高精度算法(加減乘除)的簡(jiǎn)單實(shí)現(xiàn),方便以后需要時(shí)拷貝使用。感興趣的小伙伴可以跟隨小編一起了解一下2022-12-12如何將編譯過(guò)的C++庫(kù)迅速部署在Visual?Studio新項(xiàng)目中
本文介紹在Visual?Studio中,通過(guò)屬性表,使得一個(gè)新建解決方案中的項(xiàng)目可以快速配置已有解決方案的項(xiàng)目中各類已編譯好的C++第三方庫(kù)的方法,感興趣的朋友跟隨小編一起看看吧2024-05-05C語(yǔ)言用棧模擬實(shí)現(xiàn)隊(duì)列問(wèn)題詳解
本片文章帶你分析如何用兩個(gè)棧,并且只使用棧的基本功能來(lái)模擬實(shí)現(xiàn)隊(duì)列,其中同樣只實(shí)現(xiàn)隊(duì)列的基本功能,感興趣的朋友來(lái)看看吧2022-04-04