在最初學習GCC的使用的時候,提到了動态、靜态庫的建立辦法。今天就讓我們來詳細了解一番,它們之間究竟有何不同吧!
示範所用系統:centos7.6
文章目錄
- 1.動态庫和靜态庫
- 2.生成
- 2.1 靜态庫
- 2.2 動态庫
- 2.3 一并釋出
- 3.使用
- 3.1 靜态庫
- 特點
- 3.2 動态庫
- ldd指令
- 動态連結
- 3.3 找到動态庫
- 3.3.1 環境變量LD_LIBRARY_PATH
- 3.3.2 /etc/ld.so.conf.d
- 3.3.3 在lib64下建立一個軟連接配接
- 4.優劣
- 4.1 靜态庫
- 4.2 動态庫
- 5.動态庫-fPIC的作用
- 結語
1.動态庫和靜态庫
先來了解一下動态庫和靜态庫的基本概念吧!
- 靜态庫
程式編譯連結的時候,把靜态庫的代碼連接配接到自己的可執行程式中,程式運作的時候将不再需要靜态庫.a
- 動态庫
程式在運作的時候才去連結動态庫的代碼,多個程式共享庫的代碼.so
2.生成
測試所用代碼 👉 點我
我寫好了兩個頭檔案和兩個源檔案,為了減少部落格篇幅,此處隻貼出
.c
的函數實作
//myMath.c
#include"myMath.h"
int Add(int a,int b)
{
return a+b;
}
//myPrint.c
#include "myPrint.h"
void Print(const char* msg)
{
printf("time: %d, msg: %s\n",(unsigned int)time(NULL),msg);
}
2.1 靜态庫
生成靜态庫所用指令為
ar -rc
,對應的完整make操作如下
libMytest.a:myMath.o myPrint.o
ar -rc libMytest.a myMath.o myPrint.o
myMath.o:myMath.c
gcc -c myMath.c -o myMath.o
myPrint.o:myPrint.c
gcc -c myPrint.c -o myPrint.o
生成好靜态庫後,我們可以用
ar -tv
指令來檢視該庫的目錄清單
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫]$ ar -tv libmytest.a
rw-rw-r-- 1001/1001 1240 Nov 3 09:28 2022 myMath.o
rw-rw-r-- 1001/1001 1632 Nov 3 09:28 2022 myPrint.o
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫]$
2.2 動态庫
動态庫的生成無需額外的指令,隻需要在gcc編譯的時候,指定
-shared
即可
同時,依賴的
.o
檔案也需要用
-fPIC
來編譯
-fPIC 與位置無關碼,和動态庫的特性有關
-shared 代表需要編譯一個動态庫
其make操作如下
libmytest.so:myMath.o myPrint.o
gcc -shared -o libmytest.so myMath.o myPrint.o
myMath.o:myMath.c
gcc -fPIC -c myMath.c -o myMath.o
myPrint.o:myPrint.c
gcc -fPIC -c myPrint.c -o myPrint.o
2.3 一并釋出
這裡我寫了一個更加完整的makefile,可以同時編譯生成動靜态庫,并将其打包到一個指定的檔案夾内
.PHONY:all
all:libmytest.so libmytest.a
.PHONY:lib
lib:
mkdir -p lib-static/lib
mkdir -p lib-static/include
cp *.a lib-static/lib
cp *.h lib-static/include
mkdir -p lib-dynamic/lib
mkdir -p lib-dynamic/include
cp *.so lib-dynamic/lib
cp *.h lib-dynamic/include
libmytest.so:myMath.o myPrint.o
gcc -shared -o libmytest.so myMath.o myPrint.o
myMath.o:myMath.c
gcc -fPIC -c myMath.c -o myMath.o
myPrint.o:myPrint.c
gcc -fPIC -c myPrint.c -o myPrint.o
libmytest.a:myMath.o myPrint.o
ar -rc libmytest.a myMath.o myPrint.o
myMath_s.o:myMath.c
gcc -c myMath.c -o myMath_s.o
myPrint_s.o:myPrint.c
gcc -c myPrint.c -o myPrint_s.o
.PHONY:clean
clean:
rm -rf *.o *.a *.so lib-static lib-dynamic
3.使用
#include "myPrint.h"
#include "myMath.h"
#include "stdio.h"
int main()
{
printf("ret %d\n",Add(1,2));
Print("這是一個測試");
return 0;
}
當我們使用了動靜态庫後,就沒有辦法直接編譯這個可執行程式了
muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$ gcc test.c
/tmp/ccKHwYHv.o: In function `main':
test.c:(.text+0xf): undefined reference to `Add'
test.c:(.text+0x2a): undefined reference to `Print'
collect2: error: ld returned 1 exit status
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$
這是因為,gcc沒辦法找到我們對應的頭檔案
-
是在目前路徑下找""
-
是在庫目錄下面找<>
因為我們的頭檔案既不在目前路徑,也不在系統的庫中,是以gcc就沒有辦法找到頭檔案和函數聲明
3.1 靜态庫
連結靜态庫的方法如下
gcc test.c -L../lib-static/lib/ -I../lib-static/include/ -lmytest -o test
-
選項後帶的是庫的路徑-L
-
選擇後帶的是頭檔案的搜尋路徑-I
-
選項帶的是庫的名字,需要去掉庫檔案名前面的-l(小寫的L)
和字尾lib
.a
-
代表生成可執行檔案名為test-o test
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$ gcc test.c -L../lib-static/lib/ -I ../lib-static/include/ -lmytest -o test
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$ ./test
ret 3
time: 1667441311, msg: 這是一個測試
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$
特點
靜态庫的特點便是,其庫的實作已經被編譯連結進入了可執行程式,即便我們将庫給删除,也不影響可執行程式的運作
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫]$ make clean
rm -rf *.o *.a *.so lib-static lib-dynamic
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫]$ ll
total 24
-rw-rw-r-- 1 muxue muxue 702 Nov 3 09:28 makefile
-rw-rw-r-- 1 muxue muxue 60 Nov 3 08:52 myMath.c
-rw-rw-r-- 1 muxue muxue 35 Nov 3 08:51 myMath.h
-rw-rw-r-- 1 muxue muxue 117 Nov 3 09:01 myPrint.c
-rw-rw-r-- 1 muxue muxue 77 Nov 3 09:01 myPrint.h
drwxrwxr-x 2 muxue muxue 4096 Nov 3 09:50 test
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫]$ ./test/./test
ret 3
time: 1667440486, msg: 這是一個測試
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫]$
如果我們把自己的庫的實作丢入了系統的庫目錄下(一般是
/lib64/
)編譯的時候就不需要帶
-L
選項了,隻需要用
-l
指定庫名即可
gcc test.c -lmytest
但是将自己的庫丢入系統庫路徑下的操作并不推薦,就和你将自己的可執行程式丢入
/usr/bin
路徑裡面一樣,會污染系統的環境
3.2 動态庫
動态庫和靜态庫連結的基本方式是一樣的
gcc test.c -L../lib-dynamic/lib/ -I ../lib-dynamic/include/ -lmytest -o testd
這裡選項的含義和上面完全一緻,不同的是運作的時候
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$ ./testd
./testd: error while loading shared libraries: libmytest.so: cannot open shared object file: No such file or directory
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$
直接運作,你會發現報錯了!這個報錯的大概意思就是找不到動态庫檔案
ldd指令
使用
ldd
指令檢視
testd
可執行檔案的動态庫結構,會發現我們自己的庫是沒有找到的
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$ ldd testd
linux-vdso.so.1 => (0x00007ffd051fe000)
/$LIB/libonion.so => /lib64/libonion.so (0x00007f7de6d19000)
libmytest.so => not found
libc.so.6 => /lib64/libc.so.6 (0x00007f7de6832000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f7de662e000)
/lib64/ld-linux-x86-64.so.2 (0x00007f7de6c00000)
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$
這是因為,動态庫的特點便是運作的時候也需要指定!這是一個動态連結的過程!
動态連結
動态庫需要執行
動态連結
:在可執行程式開始運作之前,外部函數的機器碼由作業系統從磁盤上的該動态庫複制到記憶體中
剛剛我們的指定隻是告訴了
gcc
編譯器庫路徑在哪兒,但是可執行程式運作的時候并不知道!
那麼如何讓可執行程式找到我們的動态庫呢?
- 将動态庫拷貝到系統的
檔案夾中/lib64
- 通過修改環境變量的方式,類似于
,可執行程式運作的時候,會自動到PATH
裡面找動态庫LD_LIBRARY_PATH
- 修改系統配置檔案
3.3 找到動态庫
3.3.1 環境變量LD_LIBRARY_PATH
和修改
PATH
的環境變量一樣,我們可以通過修改環境變量的方式增加動态庫的查找路徑
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/muxue/git/linux/code/22-11-02_動靜态庫/lib-dynamic/lib/
修改了之後的環境變量如下
LD_LIBRARY_PATH=:/home/muxue/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/muxue/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/muxue/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/muxue/git/linux/code/22-11-02_動靜态庫/lib-dynamic/lib/
再次運作
./testd
成功執行!
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/muxue/git/linux/code/22-11-02_動靜态庫/lib-dynamic/lib/
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$ ./testd
ret 3
time: 1667443224, msg: 這是一個測試
修改配置檔案的辦法,便是将該路徑永久寫入環境變量(修改環境變量的操作隻對目前bash有效)這裡就不示範辣!
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$ ldd testd
linux-vdso.so.1 => (0x00007ffde04ea000)
/$LIB/libonion.so => /lib64/libonion.so (0x00007fe9000bc000)
libmytest.so => /home/muxue/git/linux/code/22-11-02_動靜态庫/lib-dynamic/lib/libmytest.so (0x00007fe8ffda1000)
libc.so.6 => /lib64/libc.so.6 (0x00007fe8ff9d3000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007fe8ff7cf000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe8fffa3000)
ldd指令的結果也顯示出了我們自己寫的動态庫的路徑
3.3.2 /etc/ld.so.conf.d
除了修改環境變量,我們還可以修改
/etc/ld.so.conf.d
下的檔案
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$ ls /etc/ld.so.conf.d
bind-export-x86_64.conf kernel-3.10.0-1160.62.1.el7.x86_64.conf kernel-3.10.0-1160.76.1.el7.x86_64.conf
dyninst-x86_64.conf kernel-3.10.0-1160.71.1.el7.x86_64.conf mariadb-x86_64.conf
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$
這裡的操作非常簡單,我們隻需要在該目錄下新增一個
.conf
檔案,并在裡面寫入動态庫的絕對路徑即可!
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$ ls /etc/ld.so.conf.d
bind-export-x86_64.conf kernel-3.10.0-1160.62.1.el7.x86_64.conf kernel-3.10.0-1160.76.1.el7.x86_64.conf mytest.conf
dyninst-x86_64.conf kernel-3.10.0-1160.71.1.el7.x86_64.conf mariadb-x86_64.conf
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$ cat /etc/ld.so.conf.d/mytest.conf
/home/muxue/git/linux/code/22-11-02_動靜态庫/lib-dynamic/lib/
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$
設定了之後,第一次運作,還是顯示找不到動态庫
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$ ./testd
./testd: error while loading shared libraries: libmytest.so: cannot open shared object file: No such file or directory
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$
我們隻需要執行下面的指令讓配檔案生效,就OK了!
sudo ldconfig #子使用者權限不夠,需要加sudo
執行完該指令後,可執行程式也能成功運作了1
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$ sudo ldconfig
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$ ./testd
ret 3
time: 1667448942, msg: 這是一個測試
[muxue@bt-7274:~/git/linux/code/22-11-02_動靜态庫/test]$
測試完畢之後,建議将配置檔案删除,并重新加載動态庫配置檔案
sudo rm /etc/ld.so.conf.d/mytest.conf
sudo ldconfig
這樣做是避免污染
3.3.3 在lib64下建立一個軟連接配接
ln -s /home/muxue/git/linux/code/22-11-02_動靜态庫/lib-dynamic/lib/libmytest.so /lib64/libmytest.so
建立軟連接配接的方式和将我們的檔案複制進去本質是一樣的,隻不過軟連接配接隻是一個快捷方式,如果我們把源給删了,軟連接配接也會失效
這部分就不做示範了
4.優劣
4.1 靜态庫
靜态庫編譯之後的可執行程式可以脫離靜态庫運作,也不需要知道庫的路徑。
即便這個庫被删除,也絲毫不影響我們的可執行程式
4.2 動态庫
動态庫的代碼隻需要一份,所有的可執行程式便都可以使用
在運作期間,動态庫可以被多個程序所共享。但前提是,可執行程式需要知道該動态庫的路徑,以便将其加載到記憶體中(或者找到它在記憶體中的位置)
這樣就保證了多個程序同時使用同一個庫,節省了記憶體的消耗,也節省了磁盤空間
這裡動态庫的可執行檔案大小,小于靜态庫的可執行檔案
因為測試的代碼不多,是以差距尚不明顯
5.動态庫-fPIC的作用
gcc -fPIC -c myMath.c -o myMath.o
fPIC 的全稱是
Position Independent Code
, 用于生成位置無關代碼
什麼是位置無關代碼?
個人了解是代碼無絕對跳轉,跳轉都為相對跳轉
如果我們的靜态庫中,不使用其他庫的代碼(比如
stdio.h
)
int fuc(int a)
{
return ++a;
}
這時候,就可以再編譯的時候不帶
-fPIC
否則會報錯
/usr/bin/ld: /tmp/ccCViivC.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC
/tmp/ccCViivC.o: could not read symbols: Bad value
但顯然,這種情況是非常少見的,是以我們一般編譯動态庫的時候,都需要帶上這個參數,來實作真正意義上的動态庫編譯
- 加 fPIC 選項生成的動态庫,顯然是位置無關的,這樣的代碼本身就能被放到線性位址空間的任意位置,無需修改就能正确執行。通常的方法是擷取指令指針的值,加上一個偏移得到全局變量 / 函數的位址。
- 加 fPIC 選項的源檔案對于它引用的函數頭檔案編寫有很寬松的尺度。比如隻需要包含個聲明的函數的頭檔案,即使沒有相應的 C 檔案來實作,編譯成 so 庫照樣可以通過。
- 對于不加 fPIC,則加載 so 檔案時,需要對代碼段引用的資料對象需要重定位,重定位會修改代碼段的内容,這就造成每個使用這個 .so 檔案代碼段的程序在核心裡都會生成這個 .so 檔案代碼段的 copy。每個 copy 都不一樣,取決于這個 .so 檔案代碼段和資料段記憶體映射的位置。這種方式更消耗記憶體,優點是加載速度可能會快一丢丢,
弊大于利
結語
sudo yum install -y boost-devel