1. 程式人生 > > C/C++ 靜態連結庫(.a) 與 動態連結庫(.so)

C/C++ 靜態連結庫(.a) 與 動態連結庫(.so)

平時我們寫程式都必須 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分成多個選項,然後傳遞給連結程式。

參考:

.