1. 程式人生 > 實用技巧 >【CPP】靜態庫與動態庫

【CPP】靜態庫與動態庫

靜態庫與動態庫

靜態庫與動態庫的比較

函式庫本質是一組函式的集合,具有相近的功能或操作。根據連結時期的不同,函式庫分為靜態庫和動態庫。

靜態庫是指連結時,把庫檔案連結到可執行檔案中,因此生成的檔案比較大,但在執行時也就不再需要庫檔案了。靜態庫字尾名一般為.a。動態庫與之相反,在連結時並沒有把庫檔案連結到可執行檔案中,而是僅簡單地引用其函式宣告,在程式執行時根據需要動態連結庫檔案,這樣可以節省系統的開銷。動態庫字尾名一般為.so

靜態庫和動態庫的最大區別在於,在靜態情況下,把庫直接載入到程式中;而動態庫連結的時候,它只是保留介面宣告,將動態庫與程式程式碼獨立,這樣就可以提高程式碼的可複用度,和降低程式的耦合度。

  • 靜態庫可以降低執行依賴,但是會增大程式體積,並且耦合性高。例如,100 份程式都用到靜態庫 a,靜態庫 a 會被拷貝 100 份。並且一旦靜態庫需要修改,需要修改 100 份。
  • 動態庫提高程式碼的複用度,減少程式體積,但是程式執行時需要安裝相應的動態庫。例如,100 份程式都用到動態庫 a,它們會共用 1 份動態庫 a,並且修改方便。但是需要執行程式時,系統必須安裝有動態庫 a。

gcc 介紹

Linux 系統下的 gcc 是 GNU 推出的功能強大、效能優越的多平臺編譯器。gcc 是可以在多種硬體平臺上編譯出可執行程式的超級編譯器,其執行效率與一般的編譯器相比平均效率要高 20%~30%。gcc 編譯器能將 C、C++ 語言源程式、匯程式化序和目標程式編譯、連線成可執行檔案,如果沒有給出可執行檔案的名字,gcc 將生成一個名為 a.out 的可執行檔案。

靜態庫是目標檔案的一個集合,目標檔案中通常僅解析了檔案內部的變數和函式,對於引用的函式和變數還沒有解析,這需要將其他已經編寫好的目標檔案引用進來,將沒有解析的函式和變數進行解析,通常引用的目標是庫。

gcc 編譯流程

gcc 編譯原始碼時,會依次經過預處理-編譯-彙編-連結這 4 個流程。

  • 預處理,對原始碼中的檔案包含、預編譯語句等進行分析,生成預編譯檔案:gcc -E hello.c -o hello.i
  • 編譯,生成彙編檔案:gcc -S hello.i -o hello.s
  • 彙編,生成目標檔案:gcc -c hello.s -o hello.o
  • 連結,生成可執行檔案:gcc hello.o -o hello

gcc 引數表

引數 含義
-E 只預處理,不編譯,生成編譯程式碼.i
-S 只編譯,不彙編,生成彙編程式碼.s
-c 只編譯,不連結,生成目標檔案.o
-o file 把輸出檔案輸出到 file 裡
-v 列印 gcc 的版本
-I dir 在標頭檔案的搜尋路徑列表中加入 dir 目錄
-L dir 在庫檔案的搜尋路徑列表中加入 dir 目錄
-static 顯式連結靜態庫(靜態庫也可以用動態庫的方式連結)
-lxxx 連結名為 libxxx.so 的動態庫

gcc 預設連結動態庫。當有靜態庫與動態庫同名時,預設連結動態庫,如需指定靜態庫,需要加-static引數。

靜態庫

靜態庫的名字是libxxx.a,其中 xxx 是該 lib 的名稱。

生成靜態庫

在 UNIX 中,使用ar -rc libxxx.a xxx1.o xxx2.o命令建立或者操作靜態庫。

引數 含義
-r 將目標檔案插入靜態庫尾或者替換靜態庫中同名檔案
-c 建立靜態庫檔案

使用靜態庫的例子

// add.h
#ifndef _ADD_H
#define _ADD_H
int add(int a, int b);
#endif
// add.cpp
#include "add.h"
int add(int a, int b)
{
    return a + b;
}
// sub.h
#ifndef _SUB_H
#define _SUB_H
int sub(int a, int b);
#endif
// sub.cpp
#include "sub.h"
int sub(int a, int b)
{
    return a-b;
}
// main.cpp
#include "add.h"
#include "sub.h"
#include <iostream>

int main()
{
    std::cout << add(1, 2) << std::endl;
    std::cout << sub(1, 2) << std::endl;
}
  • g++ -c add.cpp, g++ -c sub.cpp,將原始碼編譯成.o檔案。
  • ar -cr libmymath.a add.o sub.o,將.o檔案生成靜態庫。
  • g++ main.cpp -L ./ -static -lmymath,說明是靜態庫,預設是載入動態庫的
  • 或者g++ main.cpp libmymath.a,此時不用指定靜態庫。

動態庫

動態庫的名字是libxxx.so.major.minor,xxx 是該 lib 的名稱,major 是主版本號,minor 是副版本號。版本號也可以沒有,一般都會建立個沒有版本號的軟連線檔案連結到全名的庫檔案。

動態連結庫是目標檔案的集合,目標檔案在動態連結庫中的組織方式是按照特殊方式形成的。庫中函式和變數的地址是相對地址,不是絕對地址,其真實地址在呼叫動態庫的程式載入時形成。

生成動態庫

不同的 Linux 系統,連結動態庫方法,實現細節不一樣。編譯 PIC 型.o中間檔案的方法一般是採用 C 語言編譯器的-KPIC或者-fPIC選項,有的 Linux 版本 C 語言編譯器預設帶上了 PIC 標準。建立最終動態庫的方法一般採用 C 語言編譯器的-G 或者-shared 選項,g++ -fPIC -shared -o libmymath.so add.cpp sub.cpp

引數 含義
-shared 指定生成動態連線庫
-fPIC 編譯為位置獨立的程式碼,跨平臺

使用動態庫的例子

原始碼同靜態庫。

  • g++ -fPIC -shared -o libmymath.so add.cpp sub.cpp,生成動態庫
  • g++ main.cpp -L ./ -lmymath
  • 或者g++ main.cpp libmymath.so

庫的查詢與載入

系統預設的標頭檔案的搜尋路徑一般為/usr/include, /usr/local/include, /usr/lib/gcc/i486-Linux-gun/4.1.2/include

系統預設的庫檔案的搜尋路徑一般為/lib, /usr/lib,具體定義在檔案/etc/ld.so.conf或者環境變數LD_LIBRARY_PATH中。

連結時

  • 顯式指定庫的路徑。gcc main.cpp ../lib/libxxx1.a ../lib/libxxx2.so
  • 或者通過引數指定庫的路徑。gcc main.cpp -L ../lib/ -lxx1 -lxx2
    • -L ../lib/:指定庫的搜尋路徑。
    • -lxx1 -lxx2:指定庫檔名。

執行時

由於靜態庫在連結時已經寫入可執行程式,故執行時不需要額外操作了。

但是動態庫需要執行時動態地按需呼叫,與連結時指定的方法不同,執行時則需要系統自行搜尋,搜尋的路徑一般有兩種方式指定:

  • 第一種方式。由檔案/etc/ld.so.conf來指定,一般預設是/lib、/usr/lib,所以要想讓動態庫順利載入,可以將庫檔案拷貝到上面的兩個目錄下或者追加自己的動態庫目錄到/etc/ld.so.conf檔案後再ldconfig重新整理即可。
  • 第二種方式。設定LD_LIBRARY_PATH環境變數,新增自己的動態庫的目錄後再source .bashrc重新整理即可。

另外還有個檔案需要了解/etc/ld.so.cache,裡面儲存了常用的動態函式庫,且會先把他們載入到記憶體中,因為記憶體的訪問速度遠遠大於硬碟的訪問速度,這樣可以提高軟體載入動態函式庫的速度了。