文章目錄
- 動靜態庫的基本原理
- 認識動靜態庫
- 動靜態庫各自的特征
- 靜態庫的打包與使用
- 打包
- 使用
- 動態庫的打包與使用
- 打包
- 使用
動靜態庫的基本原理
動靜態庫的本質是可執行程式的“半成品”,
我們都知道,一堆源檔案和頭檔案最終變成一個可執行程式需要經歷以下四個步驟:
- 預處理: 完成頭檔案展開、去注釋、宏替換、條件編譯等,最終形成
xxx.i檔案, - 編譯: 完成詞法分析、語法分析、語意分析、符號匯總等,檢查無誤后將代碼翻譯成匯編指令,最終形成
xxx.s檔案, - 匯編: 將匯編指令轉換成二進制指令,最終形成
xxx.o檔案, - 鏈接: 將生成的各個
xxx.o檔案進行鏈接,最終形成可執行程式,
例如,用test1.c、test2.c、test3.c、test4.c以及main1.c形成可執行檔案,我們需要先得到各個檔案的目標檔案test1.o、test2.o、test3.o、test4.o以及main1.o,然后再將這寫目標檔案鏈接起來,最終形成一個可執行程式,

如果我們在另一個專案當中也需要用到test1.c、test2.c、test3.c、test4.c和專案的main2.c或者main3.c分別形成可執行程式,那么可執行程式生成的步驟也是一樣的,

而實際上,對于可能頻繁用到的源檔案,比如這里的test1.c、test2.c、test3.c、test4.c,我們可以將它們的目標檔案test1.o、test2.o、test3.o、test4.o進行打包,之后需要用到這四個目標檔案時就可以之間鏈接這個包當中的目標檔案了,而這個包實際上就可以稱之為一個庫,

實際上,所有庫本質都是一堆目標檔案(xxx.o)的集合,庫的檔案當中并不包含主函式而只是包含了大量的方法以供呼叫,所以說動靜態庫本質是可執行程式的“半成品”,
認識動靜態庫
在Linux下創建檔案撰寫以下代碼,并生成可執行程式,
#include <stdio.h>
int main()
{
printf("hello world\n"); //庫函式
return 0;
}
這是最簡單的代碼,運行結果大家也都知道,就是hello world,

下面我們就通過這份簡單的代碼來認識一下動靜態庫
在這份代碼當中我們可以通過呼叫printf輸出hello world,主要原因是gcc編譯器在生成可執行程式時,將C標準庫也鏈接進來了,
在Linux下,我們可以通過ldd 檔案名來查看一個可執行程式所依賴的庫檔案,

這其中的libc.so.6就是該可執行程式所依賴的庫檔案,我們通過ls命令可以發現libc.so.6實際上只是一個軟鏈接,

實際上該軟鏈接的源檔案libc-2.17.so和libc.so.6在同一個目錄下,為了進一步了解,我們可以通過file 檔案名命令來查看libc-2.17.so的檔案型別,

此時我們可以看到,libc-2.17.so實際上就是一個共享的目標檔案庫,準確來說,這還是一個動態庫,
- 在Linux當中,以
.so為后綴的是動態庫,以.a為后綴的是靜態庫, - 在Windows當中,以
.dll為后綴的是動態庫,以.lib為后綴的是靜態庫,
這里可執行程式所依賴的libc.so.6實際上就是C動態庫,當我們去掉一個動靜態庫的前綴lib,再去掉后綴.so或者.a及其后面的版本號,剩下的就是這個庫的名字,
而gcc/g++編譯器默認都是動態鏈接的,若想進行靜態鏈接,可以攜帶一個-static選項,
[cl@VM-0-15-centos testlib]$ gcc -o mytest-s mytest.c -static

此時生成的可執行程式就是靜態鏈接的了,可以明顯發現靜態鏈接生成的可執行程式的檔案大小,比動態鏈接生成的可執行程式的檔案大小要大得多,
靜態鏈接生成的可執行程式并不依賴其他庫檔案,此時當我們使用ldd 檔案名命令查看該可執行程式所依賴的庫檔案時就會看到以下資訊,

此外,當我們分別查看動靜態鏈接生成的可執行程式的檔案型別時,也可以看到它們分別是動態鏈接和靜態鏈接的,

動靜態庫各自的特征
靜態庫
靜態庫是程式在編譯鏈接的時候把庫的代碼復制到可執行檔案當中的,生成的可執行程式在運行的時候將不再需要靜態庫,因此使用靜態庫生成的可執行程式的大小一般比較大,
優點:
- 使用靜態庫生成可執行程式后,該可執行程式就可以獨自運行,不再需要庫了,
缺點:
- 使用靜態庫生成可執行程式會占用大量空間,特別是當有多個靜態程式同時加載而這些靜態程式使用的都是相同的庫,這時在記憶體當中就會存在大量的重復代碼,
動態庫
動態庫是程式在運行的時候才去鏈接相應的動態庫代碼的,多個程式共享使用庫的代碼,一個與動態庫鏈接的可執行檔案僅僅包含它用到的函式入口地址的一個表,而不是外部函式所在目標檔案的整個機器碼,
在可執行檔案開始運行前,外部函式的機器碼由作業系統從磁盤上的該動態庫中復制到記憶體中,這個程序稱為動態鏈接,動態庫在多個程式間共享,節省了磁盤空間,作業系統采用虛擬記憶體機制允許物理記憶體中的一份動態庫被要用到該庫的所有行程共用,節省了記憶體和磁盤空間,

優點:
- 節省磁盤空間,且多個用到相同動態庫的程式同時運行時,庫檔案會通過行程地址空間進行共享,記憶體當中不會存在重復代碼,
缺點:
- 必須依賴動態庫,否則無法運行,
靜態庫的打包與使用
為了更容易理解,下面演示動靜態庫的打包與使用時,都以下面的四個檔案為例,其中兩個源檔案add.c和sub.c,兩個頭檔案add.h和sub.h,
add.h當中的內容如下:
#pragma once
extern int my_add(int x, int y);
add.c當中的內容如下:
#include "add.h"
int my_add(int x, int y)
{
return x + y;
}
sub.h當中的內容如下:
#pragma once
extern int my_sub(int x, int y);
sub.c當中的內容如下:
#include "sub.h"
int my_sub(int x, int y)
{
return x - y;
}
代碼內容都非常簡單,我就不做過多的闡述了,
打包
下面我們就利用這四個檔案打包生成一個靜態庫:

第一步:讓所有源檔案生成對應的目標檔案

第二步:使用ar命令將所有目標檔案打包為靜態庫
ar命令是gnu的歸檔工具,常用于將目標檔案打包為靜態庫,下面我們使用ar命令的-r選項和-c選項進行打包,
-r(replace):若靜態庫檔案當中的目標檔案有更新,則用新的目標檔案替換舊的目標檔案,-c(create):建立靜態庫檔案,
[cl@VM-0-15-centos static]$ ar -rc libcal.a add.o sub.o

此外,我們可以用ar命令的-t選項和-v選項查看靜態庫當中的檔案,
-t:列出靜態庫中的檔案,-v(verbose):顯示詳細的資訊,
[cl@VM-0-15-centos static]$ ar -tv libcal.a

第三步:將頭檔案和生成的靜態庫組織起來
當我們把自己的庫給別人用的時候,實際上需要給別人兩個檔案夾,一個檔案夾下面放的是一堆頭檔案的集合,另一個檔案夾下面放的是所有的庫檔案,
因此,在這里我們可以將add.h和sub.h這兩個頭檔案放到一個名為include的目錄下,將生成的靜態庫檔案libcal.a放到一個名為lib的目錄下,然后將這兩個目錄都放到mathlib下,此時就可以將mathlib給別人使用了,

使用Makefile
當然,我們可以將上述所要執行的命令全部寫到Makefile當中,后續當我們要生成靜態庫以及組織頭檔案和庫檔案時就可以一步到位了,不至于每次重新生成的時候都要敲這么多命令,這也體現了Makefile的強大,

撰寫Makefile后,只需一個make就能生成所有源檔案對應的目標檔案進而生成靜態庫,

一個make output就能將頭檔案和靜態庫組織起來,

使用
創建源檔案main.c,撰寫下面這段簡單的程式來嘗試使用我們打包好的靜態庫,
#include <stdio.h>
#include <add.h>
int main()
{
int x = 20;
int y = 10;
int z = my_add(x, y);
printf("%d + %d = %d\n", x, y, z);
return 0;
}
現在該目錄下就只有main.c和我們剛才打包好的靜態庫,

方法一:使用選項
此時使用gcc編譯main.c生成可執行程式時需要攜帶三個選項:
-I:指定頭檔案搜索路徑,-L:指定庫檔案搜索路徑,-l:指明需要鏈接庫檔案路徑下的哪一個庫,
[cl@VM-0-15-centos project]$ gcc main.c -I./mathlib/include -L./mathlib/lib -lcal

此時就可以成功使用我們自己打包的庫檔案并生成可執行程式,

說明一下:
- 因為編譯器不知道你所包含的頭檔案add.h在哪里,所以需要指定頭檔案的搜索路徑,
- 因為頭檔案add.h當中只有my_add函式的宣告,并沒有該函式的定義,所以還需要指定所要鏈接庫檔案的搜索路徑,
- 實際中,在庫檔案的lib目錄下可能會有大量的庫檔案,因此我們需要指明需要鏈接庫檔案路徑下的哪一個庫,庫檔案名去掉前綴
lib,再去掉后綴.so或者.a及其后面的版本號,剩下的就是這個庫的名字, -I,-L,-l這三個選項后面可以加空格,也可以不加空格,
方法二:把頭檔案和庫檔案拷貝到系統路徑下
既然編譯器找不到我們的頭檔案和庫檔案,那么我們直接將頭檔案和庫檔案拷貝到系統路徑下不就行了,
[cl@VM-0-15-centos project]$ sudo cp mathlib/include/* /usr/include/
[cl@VM-0-15-centos project]$ sudo cp mathlib/lib/libcal.a /lib64/

需要注意的是,雖然已經將頭檔案和庫檔案拷貝到系統路徑下,但當我們使用gcc編譯main.c生成可執行程式時,還是需要指明需要鏈接庫檔案路徑下的哪一個庫,

此時才能成功使用我們自己打包的庫檔案并生成可執行程式,

為什么之前使用gcc編譯的時候沒有指明過庫名字?
因為我們使用gcc編譯的是C語言,而gcc就是用來編譯C程式的,所以gcc編譯的時候默認就找的是C庫,但此時我們要鏈接的是哪一個庫編譯器是不知道的,因此我們還是需要使用-l選項,指明需要鏈接庫檔案路徑下的哪一個庫,
擴展:
實際上我們拷貝頭檔案和庫檔案到系統路徑下的程序,就是安裝庫的程序,但并不推薦將自己寫的頭檔案和庫檔案拷貝到系統路徑下,這樣做會對系統檔案造成污染,
動態庫的打包與使用
打包
動態庫的打包相對于靜態庫來說有一點點差別,但大致相同,我們還是利用這四個檔案進行打包演示:

第一步:讓所有源檔案生成對應的目標檔案
此時用源檔案生成目標檔案時需要攜帶-fPIC選項:
-fPIC(position independent code):產生位置無關碼,

說明一下:
-fPIC作用于編譯階段,告訴編譯器產生與位置無關的代碼,此時產生的代碼中沒有絕對地址,全部都使用相對地址,從而代碼可以被加載器加載到記憶體的任意位置都可以正確的執行,這正是共享庫所要求的,共享庫被加載時,在記憶體的位置不是固定的,- 如果不加
-fPIC選項,則加載.so檔案的代碼段時,代碼段參考的資料物件需要重定位,重定位會修改代碼段的內容,這就造成每個使用這個.so檔案代碼段的行程在內核里都會生成這個.so檔案代碼段的拷貝,并且每個拷貝都不一樣,取決于這個.so檔案代碼段和資料段記憶體映射的位置, - 不加
-fPIC編譯出來的.so是要在加載時根據加載到的位置再次重定位的,因為它里面的代碼BBS位置無關代碼,如果該.so檔案被多個應用程式共同使用,那么它們必須每個程式維護一份.so的代碼副本(因為.so被每個程式加載的位置都不同,顯然這些重定位后的代碼也不同,當然不能共享), - 我們總是用
-fPIC來生成.so,但從來不用-fPIC來生成.a,但是.so一樣可以不用-fPIC選項進行編譯,只是這樣的.so必須要在加載到用戶程式的地址空間時重定向所有表目,
第二步:使用-shared選項將所有目標檔案打包為動態庫
與生成靜態庫不同的是,生成動態庫時我們不必使用ar命令,我們只需使用gcc的-shared選項即可,
[cl@VM-0-15-centos dynamic]$ gcc -shared -o libcal.so add.o sub.o

第三步:將頭檔案和生成的動態庫組織起來
與生成靜態庫時一樣,為了方便別人使用,在這里我們可以將add.h和sub.h這兩個頭檔案放到一個名為include的目錄下,將生成的動態庫檔案libcal.so放到一個名為lib的目錄下,然后將這兩個目錄都放到mlib下,此時就可以將mlib給別人使用了,

使用Makefile
當然,生成動態庫也可以將上述所要執行的命令全部寫到Makefile當中,后續當我們要生成動態庫以及組織頭檔案和庫檔案時就可以一步到位了,

撰寫Makefile后,只需一個make就能生成所有源檔案對應的目標檔案進而生成動態庫,

一個make output就能將頭檔案和動態庫組織起來,

使用
我們還是用剛才使用過的main.c來演示動態庫的使用,
#include <stdio.h>
#include <add.h>
int main()
{
int x = 20;
int y = 10;
int z = my_add(x, y);
printf("%d + %d = %d\n", x, y, z);
return 0;
}
現在該目錄下就只有main.c和我們剛才打包好的動態庫,

說明一下,使用該動態庫的方法與剛才我們使用靜態庫的方法一樣,我們既可以使用 -I,-L,-l這三個選項來生成可執行程式,也可以先將頭檔案和庫檔案拷貝到系統目錄下,然后僅使用-l選項指明需要鏈接的庫名字來生成可執行程式,下面我們僅以第一種方法為例進行演示,
此時使用gcc編譯main.c生成可執行程式時,需要用-I選項指定頭檔案搜索路徑,用-L選項指定庫檔案搜索路徑,最后用-l選項指明需要鏈接庫檔案路徑下的哪一個庫,
[cl@VM-0-15-centos project]$ gcc main.c -I./mlib/include -L./mlib/lib -lcal

與靜態庫的使用不同的是,此時我們生成的可執行程式并不能直接運行,

需要注意的是,我們使用-I,-L,-l這三個選項都是在編譯期間告訴編譯器我們使用的頭檔案和庫檔案在哪里以及是誰,但是當生成的可執行程式生成后就與編譯器沒有關系了,此后該可執行程式運行起來后,作業系統找不到該可執行程式所依賴的動態庫,我們可以使用ldd命令進行查看,

可以看到,此時可執行程式所依賴的動態庫是沒有被找到的,
解決該問題的方法有以下三個:
方法一:拷貝.so檔案到系統共享庫路徑下
既然系統找不到我們的庫檔案,那么我們直接將庫檔案拷貝到系統共享的庫路徑下,這樣一來系統就能夠找到對應的庫檔案了,
[cl@VM-0-15-centos project]$ sudo cp mlib/lib/libcal.so /lib64

可執行程式也就能夠順利運行了,

方法二:更改
LD_LIBRARY_PATH
LD_LIBRARY_PATH是程式運行動態查找庫時所要搜索的路徑,我們只需將動態庫所在的目錄路徑添加到LD_LIBRARY_PATH環境變數當中即可,
[cl@VM-0-15-centos project]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/cl/BasicIO/testlib/project/mlib/lib

此時我們再用ldd命令查看該可執行程式就會發現,系統現在就可以找到該可執行程式所依賴的動態庫了,

現在我們也就能正常運行該可執行程式了,

方法三:配置
/etc/ld.so.conf.d/
我們可以通過配置/etc/ld.so.conf.d/的方式解決該問題,/etc/ld.so.conf.d/路徑下存放的全部都是以.conf為后綴的組態檔,而這些組態檔當中存放的都是路徑,系統會自動在/etc/ld.so.conf.d/路徑下找所有組態檔里面的路徑,之后就會在每個路徑下查找你所需要的庫,我們若是將自己庫檔案的路徑也放到該路徑下,那么當可執行程式運行時,系統就能夠找到我們的庫檔案了,
首先將庫檔案所在目錄的路徑存入一個以.conf為后綴的檔案當中,

然后將該.conf檔案拷貝到/etc/ld.so.conf.d/目錄下,

但此時我們用ldd命令查看可執行程式時,發現系統還是沒有找到該可執行程式所依賴的動態庫,

這時我們需要使用ldconfig命令將組態檔更新一下,更新之后系統就可以找到該可執行程式所依賴的動態庫了,
[cl@VM-0-15-centos project]$ sudo ldconfig

而此時我們也就可以正常運行該可執行程式了,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/342212.html
標籤:其他
下一篇:18 Linux執行緒
