gcc——靜態庫與動態庫應用
gcc——靜態庫與動態庫應用
文章目錄
1. 基礎知識
關於 gcc
編譯器命令詳解,我會專門更新一篇博文去從頭到尾講述它的原理、用法及命令。
那本篇文章主要是針對 gcc
靜態庫與動態庫作一番總結,網上很多 gcc
自定義庫製作都不是非常的全面,所以這裡我們來詳細扒一扒 Linux
下 gcc
自定義庫製作方法。
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.c
、fun_b.c
、main.c
、fun.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.c
和 fun_b.c
對應生成 fun_a.o
和 fun_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 編譯器預設是使用動態庫。