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分成多個選項,然后傳遞給鏈接程序。

 

 

參考:

http://blog.csdn.net/freestyle4568world/article/details/49817799 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM