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