靜態鏈接庫與動態鏈接庫----C/C++

  平時我們寫程序都必須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。

下面我們來寫一個測試文件:

<span style="font-size:18px;">#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;
}</span>
因爲我的目錄結構是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)編譯選項

l -shared :指定生成動態鏈接庫。

l -static :指定生成靜態鏈接庫。

l -fPIC :表示編譯爲位置獨立的代碼,用於編譯共享庫。目標文件需要創建成位置無關碼,念上就是在可執行程序裝載它們的時候,它們可以放在可執行程序的內存裏的任何地方。

l -L. :表示要連接的庫所在的目錄。

l -l:指定鏈接時需要的動態庫。編譯器查找動態連接庫時有隱含的命名規則,即在給出的名字前面加上lib,後面加上.a/.so來確定庫的名稱。

l -Wall :生成所有警告信息。

l -ggdb :此選項將儘可能的生成gdb的可以使用的調試信息。

l -g :編譯器在編譯的時候產生調試信息。

l -c :只激活預處理、編譯和彙編,也就是把程序做成目標文件(.o文件)

l -Wl,options :把參數(options)傳遞給鏈接器ld。如果options中間有逗號,就將options分成多個選項,然後傳遞給鏈接程序。















發佈了65 篇原創文章 · 獲贊 75 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章