天天看點

【Linux】動态庫&靜态庫

在最初學習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​

  • ​-o test​

    ​代表生成可執行檔案名為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 動态庫

動态庫的代碼隻需要一份,所有的可執行程式便都可以使用

在運作期間,動态庫可以被多個程序所共享。但前提是,可執行程式需要知道該動态庫的路徑,以便将其加載到記憶體中(或者找到它在記憶體中的位置)

【Linux】動态庫&amp;靜态庫

這樣就保證了多個程序同時使用同一個庫,節省了記憶體的消耗,也節省了磁盤空間

【Linux】動态庫&amp;靜态庫

這裡動态庫的可執行檔案大小,小于靜态庫的可執行檔案

因為測試的代碼不多,是以差距尚不明顯

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