1. 程式人生 > 其它 >gcc——靜態庫與動態庫應用

gcc——靜態庫與動態庫應用

技術標籤:Linux高階編譯器linux

gcc——靜態庫與動態庫應用

轉載請標明出處!

文章目錄

1. 基礎知識

關於 gcc 編譯器命令詳解,我會專門更新一篇博文去從頭到尾講述它的原理、用法及命令。

那本篇文章主要是針對 gcc 靜態庫與動態庫作一番總結,網上很多 gcc 自定義庫製作都不是非常的全面,所以這裡我們來詳細扒一扒 Linuxgcc 自定義庫製作方法。

1.1 shell命令

那簡單來提一下本章內容會用到的 gcc 編譯器基本shell 命令。

  • -v(–version)
    • gcc 版本
  • -Wall
    • 檢視警告資訊
  • -c
    • 將原始檔編譯為目標檔案
  • -o
    • 將目標檔案連結為可執行檔案
  • -I / -i
    • 搜尋標頭檔案目錄/單個的位置
  • -L / l
    • 搜尋庫檔案目錄/單個的位置

1.2 檔案

  • lib_funS.a

    • 靜態庫
  • lib_funS.so

    • 動態庫
  • test

    • 可執行檔案
gcc -Wall main.c /usr/lib/lib_funS.a -o test

1.3 環境變數

檔案是在 .bash_profile 當中

  • C_INCLUDE_PATH
    • C標頭檔案路徑
  • CPLUS_INCLUDE_PATH
    • C++標頭檔案路徑
  • LIBRARY_PATH
    • 告訴作業系統,要到這個地方查詢動態庫,連結期間搜尋靜態庫。
  • LD_LIBRARY_PATH
    • 告訴作業系統,要到這個地方查詢動態庫,執行期間搜尋動態庫。

路徑與路徑之間可以加冒號來進行區分。

DIR1:DIR2:DIR3

2. 建立模板

在當前目錄下建立四個檔案,包含三個原始檔和一個頭檔案,分別為 fun_a.cfun_b.cmain.cfun.h

main.c

#include "fun.h"
int main() { fun_a(); fun_b(); return 0; }

fun_a.c

#include "fun.h"

void fun_a(void)
{
    printf("Hello Home!\n");
}

fun_b.c

#include "fun.h"

void fun_b(void)
{
    printf("Hello girl!\n");
}

fun.h

#ifndef _FUN_H
#define _FUN_H

#include "stdio.h"

void fun_a(void);
void fun_b(void);

#endif

3. 靜態庫

靜態庫從建立、連結到執行,可分為三步驟。

3.1 目標檔案

將需要生成靜態庫的所有原始檔,生成目標檔案(.o)。也就是將 fun_a.cfun_b.c 對應生成 fun_a.ofun_b.o

注意

不包括 main.c 檔案。

gcc -Wall -c fun_a.c fun_b.c

3.2 靜態庫

然後我們把所有的目標檔案(.o)結合生成靜態庫檔案,取名叫做 lib_funS.a (字尾 .a 結尾的檔案)。

ar cr lib_funS.a *.o

// 等價於(二選一)
ar cr lib_funS.a fun_a.o fun_b.o

注意

  • 最好我們命令為 lib_xxx.a ,後面我們連結生成執行檔案將會說到。

  • 我們可以通過以下命令實現查詢靜態庫包含的檔案。

ar t lib_funS.a

如圖:

3.3 連結生成執行檔案

生成了靜態庫之後,有三種方式可以實現程式的執行,那我們來仔細扒一扒能通過什麼樣的方式完成呢?

我們就可以通過靜態庫與 main.c 連結成可執行檔案 (test)。

gcc -Wall main.c lib_funS.a -o test

沒有任何報錯,我們在來執行看下結果。

./test

注意

這裡的 main.c 和 lib_funS.a` 不能夠調換位置,否則會導致出錯。

gcc -Wall lib_funS.a main.c -o test

如圖:
在這裡插入圖片描述

擴充套件

普通連結

我們除了第一種連結方式,我們還可以採用最為常規的方式連結成可執行檔案(test),就是把所有目標檔案不通過靜態庫方式。

先把 main.c 生成 main.o,因為我們在前面沒有去生成。

gcc -Wall -c main.c
gcc -Wall *.o -o test

// 等價於(二選一)
gcc -Wall main.o fun_a.o fun_b.o -o test

沒有任何報錯,我們在來執行看下結果。

./test
缺陷連結

以上兩種方法可行,第三種方法有“雷區”,需要大家注意!!!

gcc -Wall main.c -l_funS -o test

結果如圖:
在這裡插入圖片描述

這種方式是有缺陷的,什麼缺陷呢?gcc 編譯器尋找不到 lib_funS.a 這個靜態庫,預設 gcc 編譯器只會查詢預設或指定的目錄下是否存在庫檔案。

再這裡,我們說一下,gcc 預設查詢標頭檔案區域在:

  • /usr/local/include
  • /usr/include

gcc 預設查詢庫檔案區域在:

  • /usr/local/lib
  • /usr/lib

為了解決這個問題,我們有三種方式可以解決,從而達到 gcc 編譯器能夠查詢到 lib_funS.a檔案,且不會報錯。

方式一:

所以,我們需要把 lib_funS.a 這個靜態庫,移動繫到其中的一個目錄下,編譯器才不會報錯。

sudo cp lib_funS.a /usr/lib

// 等價於(二選一)
sudo cp lib_funS.a /usr/local/lib

再進行重新連結**(提醒:這裡的 l_funS 就是 lib_funS 靜態庫的縮寫。)**

gcc -Wall main.c -l_funS -o test

沒有任何報錯,我們在來執行看下結果。

./test
方式二:

那我們還可以通過命令 -L 查詢當前靜態庫的位置,相對路徑絕對路徑兩種都可以實現連結。

gcc -Wall main.c -L. -l_funS -o test

//(二選一)
gcc -Wall main.c -L/路徑 -lhello -o h3

沒有任何報錯,我們在來執行看下結果。

./test
方式三:

新增環境變數,LIBRARY_PATH 添加了靜態庫的絕對路徑,也可以實現連結。

export LIBRARY_PATH=/路徑:$LIBRARY_PATH

然後我們連結,發現並沒有報錯了。

gcc -Wall main.c -l_funS -o test

沒有任何報錯,我們在來執行看下結果。

./test
總結:

我們來說說這三種方式,我們應該採用哪種比較好?首先方式一直接修改系統庫路徑當中檔案,這點一定是不推薦的,這樣會導致你的系統庫越來越大並且會佔據更多的記憶體空間,而且萬一手抖刪掉了其它配置檔案,可能會導致系統崩潰。方式三,修改環境變數,也是不推薦,因為不利於我們做移植,每個環境下都要重新配置環境變數,可知它的麻煩。。所以我們採用方式二這種方式。

3.4 執行程式

執行 ./可執行檔案 ,就可以執行程式了。

3.5 擴充套件

檔案結構

我們之前說的都是所有檔案在同一個目錄下,那如果是一個工程專案,一定是會有若干個資料夾與檔案組成。這裡,我們以最基礎的檔案結構來說明,大家會懂得舉一反三。為了方便管理,通常標頭檔案放在 inc 資料夾當中,庫檔案放在 lib 資料夾當中。
在這裡插入圖片描述

main.c

#include "hello.h"

int main()
{
    hello();
    return 0;
}

hello.c

#include "hello.h"

void hello(void)
{
    printf("Hello World!\n");
}

hello.h

#ifndef _HELLO_H
#define _HELLO_H

#include <stdio.h>

void hello(void);
#endif

過程

將所要生成庫檔案的原始檔都生成目標檔案(.o)

gcc -Wall -c -Iinc hello.c

將目標檔案連結成靜態庫

ar cr lib_funS.a hello.o

靜態庫移動到 lib 資料夾當中。

mv lib_funS.a lib

到這裡,我們採用方式二來完成,其它方式大家可以自己嘗試。

main.c 編譯成目標檔案。

gcc -Wall -Iinc -c main.c

在將 main.o 與 靜態連結庫 生成可執行檔案。

gcc -Wall main.o -Llib -l_funS -o test

執行可執行檔案。

./test

4. 靜態庫與動態庫區別

4.1 靜態庫(static libraries)

Linux下靜態庫以後綴 .a 命名,在windows下以後綴 .lib 命名。

4.2 動態庫(shared libraries)

Linux下靜態庫以後綴 .so 命名,在windows下以後綴 .dll 命名,這裡採用方式二 來完成,其它方式請參照以上方式。

gcc -fPIC -shared hello.c -o lib_DLL.so
gcc main.c -L. -l_DLL -o test
./test

4.3 區別

如果使用靜態庫,由機器碼構成,將呼叫函式拷貝到目標檔案當中,個頭比較大,佔的記憶體比較多。而動態庫不是把機器碼拷貝到目標檔案,而是拷貝small table(內部是地址),雖增加了磁碟空間,但大大減少對記憶體的浪費。而且作業系統提供虛擬記憶體機制,使得如果有多個程序呼叫一個動態庫,只需要留一個動態庫拷貝,可以節省極大對記憶體浪費。

gcc 編譯器預設是使用動態庫。