庫是程序員必須的工具,它是已經編譯過的代碼。庫通常提供通用的功能,例如鏈表和二叉樹等數據結構,或者特定的功能,例如MySQL等數據庫服務器訪問接口。
多數大型軟件項目有多個部件組成,這些部件有可能在其它項目中用到,或者你只是想分離多個部件以便組織。當你有一個可重用或者邏輯上獨立的函數集合,建立一個庫使你不用總是複製代碼到現在的項目然後重新編譯,而且可以保持不同的模塊的分離,修改其中一個不會影響其他模塊。只要一個庫被寫好且測試過,就可以安全地重用。
建立靜態庫是相當簡單的,這裏只講如何建立共享庫(動態庫)。
在開始之前,先看看從源代碼到可執行程序這個過程中發生什麼:
1、C語言預處理:這個階段,任何以#開頭,例如#define和#include的行都會被處理,也就是宏替換和文件包含。
2、編譯:將預處理後的源代碼轉換成彙編代碼,然後在轉換成機器代碼。
3、鏈接:將objectfiles和庫鏈接生成可執行程序。對於靜態庫,鏈接器把庫本身放到最終的可執行程序中;而對於共享庫,只是把對庫的引用放到裏面。
4、裝入:當我們執行程序的時候,檢查程序對共享庫的引用,將動態庫映射到程序中。
第3和第4步是共享庫比較神奇卻也讓人困惑的地方。
foo.h:
1 #ifndef foo_h__ 2 #define foo_h__ 3 extern void foo(void); 4 #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; }
foo.h定義了我們的庫的接口,一個函數foo(),foo.c是函數實現的地方。
本例子在路徑 /home/username/foo
Step 1:編譯不依賴於位置的代碼
將庫的源代碼編譯成位置無關的代碼
$ gcc -c -Wall -Werror -fpic foo.c
Step 2: 由目標文件(object file)生成共享庫
將庫的名稱定爲 libfoo.so:
gcc -shared -o libfoo.so foo.o
Step 3: 鏈接共享庫
先編譯 main.c然後鏈接 libfoo,最終生成程序“test.”-lfoo選項是用來尋找libfoo.so的。 GCC 假定所有庫以lib開頭,以.so或者.a結尾(.so是代表共享庫,.a代表歸檔或者靜態鏈接庫)。
$ gcc -Wall -o test main.c -lfoo /usr/bin/ld: cannot find -lfoo collect2: ld returned 1 exit status
告訴GCC去哪裏找共享庫
用–L選項,在此例中,共享庫在當前目錄,也就是/home/username/foo中
$ gcc -L/home/username/foo -Wall -o test main.c -lfoo
Step 4: 使共享庫在程序運行時能用上。
$ ./test ./test: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory
因爲沒有將共享庫libfoo.so放在標準目錄,所以要給裝入器(loader)點幫助。有幾個選擇:可以用環境變量LD_LIBRARY_PATH,或者rpath。先看看LD_LIBRARY_PATH。
Using LD_LIBRARY_PATH
$ echo $LD_LIBRARY_PATH
這個變量中什麼也沒有,我們將工作目錄加到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
雖然正在LD_LIBRARY_PATH這個目錄中,但是沒有export它。在Linux,如果沒有export那些對環境變量的改變,它們的改變不會被子進程繼承,所以裝入器(loader)和test程序沒有繼承這些改變。好在,很容易解決這個問題:
$ export LD_LIBRARY_PATH=/home/username/foo:$LD_LIBRARY_PATH $ ./test This is a shared library test... Hello, I'm a shared library
Good, it worked! LD_LIBRARY_PATH 用來測試共享庫很方便,尤其是當我們沒有系統管理員權限的時候。然而,exportLD_LIBRARY_PATH意味着有可能導致其他用到LD_LIBRARY_PATH的程序出問題。
Using rpath
現在試試rpath(首先清空LD_LIBRARY_PATH,以確保是rpath找到共享庫)。rpath,也就是運行路徑(run path),是在可執行程序中嵌入共享庫位置的一種方式,而不是依賴於默認的位置或者環境變量。在鏈接的階段,用“-Wl,-rpath=/home/username/foo” 選項,-WL給定鏈接器一些選項,這些選項以逗號分隔。
$ 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很好用,因爲每個程序可以設定獨立的共享庫位置,而不會像LD_LIBRARY_PATH那樣影響其他程序。不過,rpath缺乏靈活性,它要求共享庫安裝到特定的路徑,在系統配置時不靈活。
Using ldconfig to modify ld.so
如果想安裝我們的庫讓任何系統登錄用戶可以使用,需要管理員權限,理由如下:首先,要把庫放到標準位置,可能是/usr/lib或/usr/local/lib,普通用戶沒有寫入權限;其次,要修改ld.so配置文件和緩存。以管理員登陸,執行以下命令:
$ cp /home/username/foo/libfoo.so /usr/lib $ chmod 0755 /usr/lib/libfoo.so
現在,庫文件在標準路徑下,有着可以被任何人讀取的權限。要告訴裝入器它是可用的,於是更新緩存:
$ ldconfig
上面的命令會創造一個到共享庫的鏈接,更新緩存。檢測以下:
$ ldconfig -p | grep foo libfoo.so (libc6) => /usr/lib/libfoo.so
可見我們的庫已經安裝好了。在用它之前,先清理LD_LIBRARY_PATH,以防萬一:
$ unset LD_LIBRARY_PATH
重新鏈接我們的可執行程序,不需要–L選項,因爲我們的庫已經放在默認的位置,且不需要rpath選項:
$ gcc -Wall -o test main.c -lfoo
用ldd命令,看看test是否使用了我們的庫:
$ ldd test | grep foo libfoo.so => /usr/lib/libfoo.so (0x00a42000)
然後運行:
$ ./test This is a shared library test... Hello, I'm a shared library
鏈接:http://www.cnblogs.com/wongzawing/archive/2013/02/19/2917792.html