Linux下動靜態(tài)庫的打包與使用指南(C/C++)
前言
為什么用動靜態(tài)庫
我們在實際開發(fā)中,經(jīng)常要使用別人已經(jīng)實現(xiàn)好的功能,這是為了開發(fā)效率和魯棒性(健壯性);因為那些功能都是頂尖的工程師已經(jīng)寫好的,并且已經(jīng)踐行多年的代碼。
那么如何使用他人開發(fā)的功能呢?
1.庫: 包括靜態(tài)庫與動態(tài)庫。
2.開源代碼。
3.基本的網(wǎng)絡(luò)功能調(diào)用,比如各種網(wǎng)絡(luò)接口、語音識別等等。
這其中,我們將詳細(xì)介紹靜態(tài)庫和動態(tài)庫:
為什么在實際工作中一般將源碼打包成動態(tài)靜態(tài)庫來給不同的人使用?
- 知識就是財富,你只是給某些人用一下你寫的業(yè)務(wù)功能,并不想暴露源碼的邏輯,那就打包成.obj目標(biāo)文件甩給他用;
- 你也可以自己給自己封裝庫,因為你自己編譯生成了.obj目標(biāo)文件,是已經(jīng)編譯完成了的,只需要把這個庫對應(yīng)的頭文件引入新的程序中,.obj文件放入庫路徑,這樣新的程序鏈接器就可以直接把功能鏈接起來,方便部署使用,省去了反復(fù)編譯的麻煩;
動態(tài)鏈接與靜態(tài)鏈接
一般情況下,為了更好的支持開發(fā),第三方庫或者是語言庫都必須提供靜態(tài)庫和動態(tài)庫(eg:C C++等官方庫),這是方便程序員根據(jù)需求功能進(jìn)行可執(zhí)行文件的生成;
動態(tài)鏈接使用動態(tài)庫,而靜態(tài)鏈接使用靜態(tài)庫。
一般來說,我們gcc編譯默認(rèn)是動態(tài)鏈接的而如果加上-static選項,那么生成的可執(zhí)行文件將為靜態(tài)生成;
底層優(yōu)缺點
動態(tài)鏈接文件信息:
靜態(tài)鏈接文件信息:
可以明顯發(fā)現(xiàn)動態(tài)鏈接的文件大小明顯要比靜態(tài)鏈接的文件大小要小多了
這是因為動態(tài)鏈接是當(dāng)程序執(zhí)行到調(diào)用接口時,編譯器再去特定路徑查找目標(biāo)接口的可執(zhí)行文件,直接進(jìn)行計算;
靜態(tài)鏈接比較暴力,鏈接時候直接將目標(biāo)接口的二進(jìn)制代碼全部鏈接到原文件中去,這也就是靜態(tài)鏈接生成的文件這么大的原因了;(畢竟把二進(jìn)制代碼copy過來了)
但是這些都是相對的,有優(yōu)點就有缺點:
萬一動態(tài)庫路徑中的庫丟失損壞 ,動態(tài)鏈接的程序到目標(biāo)位置了,過來用的時候肯定出錯了;
靜態(tài)鏈接因為編譯的時候吧二進(jìn)制代碼考過去了,不依賴原生庫,即便原庫代碼丟失也沒事;
小結(jié)
Linux下的動靜態(tài)庫
linux下庫的命名格式一般為:
靜態(tài)庫: lib+庫的名字+.a eg:c標(biāo)準(zhǔn)庫為 libc.a
動態(tài)庫: lib+庫的名字+.so
靜態(tài)庫是指程序在編譯鏈接的時候把庫的二進(jìn)制可執(zhí)行代碼鏈接到可執(zhí)行文件中。程序運(yùn)行的時候?qū)⒉辉傩枰o態(tài)庫。
而動態(tài)庫則是指程序在運(yùn)行的時候才去啟動指定位置的動態(tài)庫的代碼,使其加載到內(nèi)存共享區(qū)中,多個程序共享使用庫的二進(jìn)制代碼, 不用拉到本文件中來。
- 一個與動態(tài)庫鏈接的可執(zhí)行文件僅僅包含它用到的函數(shù)入口地址的一個表(頭文件),而不是外部函數(shù)所在目標(biāo)文件(.o)的整個機(jī)器碼
- 在可執(zhí)行文件開始運(yùn)行以前,外部函數(shù)的機(jī)器碼由操作系統(tǒng)從磁盤上的該動態(tài)庫中復(fù)制到內(nèi)存中,這個過程稱為動態(tài)鏈接(dynamic linking),也就是說,動態(tài)鏈接是在需要調(diào)用接口時才會去將所用接口的二進(jìn)制代碼拷貝到內(nèi)存中。
- 當(dāng)一個庫多文件使用時,動態(tài)庫只有一份,所以可以在多個程序間共享,所以動態(tài)鏈接使得可執(zhí)行文件更小,節(jié)省了磁盤空間。–>操作系統(tǒng)采用虛擬內(nèi)存機(jī)制允許物理內(nèi)存中的一份動態(tài)庫被要用到該庫的所有進(jìn)程共用,節(jié)省了內(nèi)存和磁盤空間。
- 這里需要提一下的是,我們之前所提過的進(jìn)程地址空間中有一個共享區(qū),而一般動態(tài)庫的代碼就映射在共享區(qū),所有進(jìn)程都共享著動態(tài)庫的代碼。
動靜態(tài)庫的對比
動態(tài)庫被加載在內(nèi)存中,可以供多個使用庫的程序共享映射到自己的虛擬地址空間使用,因此可以減少頁面交換以及降低內(nèi)存中代碼冗余,并且因為與源程序模塊分離,因此開發(fā)模式比較好。
而加載動態(tài)庫的程序運(yùn)行速度相對較慢,因為動態(tài)庫運(yùn)行時加載,映射到虛擬地址空間后需要重新根據(jù)映射起始地址計算函數(shù)/變量地址。
靜態(tài)庫直接把二進(jìn)制代碼鏈接過來,與動態(tài)庫的使用恰好相反,其運(yùn)行速度相對較快,但消耗資源較多。
打包靜態(tài)庫
庫函數(shù)源文件:
//file1: Add.c #include<stdio.h> int Add(int a,int b) { return a+b; } //file2: Sub.c #include<stdio.h> int Sub(int a,int b) { return a-b; }
生成靜態(tài)庫需要先生成目標(biāo)文件(.o)再進(jìn)行打包,故先編寫相應(yīng)的源文件再將其編譯成目標(biāo)文件:
//生成兩個二進(jìn)制目標(biāo)文件 [root@VM-8-15-centos fighting]# gcc -c Sub.c -o Sub.o [root@VM-8-15-centos fighting]# gcc -c Add.c -o Add.o
此時的add.o和sub.o文件是已經(jīng)編譯好但還沒有鏈接的兩個文件;
此時再用 ar命令,歸檔工具將其打包成靜態(tài)庫:
//將這倆二進(jìn)制目標(biāo)文件打包成靜態(tài)庫 [lyl@VM-4-3-centos 2022-3-14]$ ar -rc libmycal.a Add.o Sub.o //`rc`表示(replace and create)
查看靜態(tài)庫
//查看靜態(tài)庫的目錄列表 [root@VM-8-15-centos fighting]# ar -tv libmycal.a `tv`表示(列出靜態(tài)庫中文件 and verbose詳細(xì)信息) rw-r--r-- 0/0 936 Jan 24 16:57 2023 Add.o rw-r--r-- 0/0 1240 Jan 24 16:57 2023 Sub.o
使用靜態(tài)庫
將庫的頭文件和靜態(tài)庫都放到指定lib目錄下:
調(diào)用我們的庫接口代碼:
#include <stdio.h> #include "add.h" #include "sub.h" int main() { int a = 10; int b = 20; printf("a+b:%d\n", Add(a, b)); printf("a-b:%d\n", Sub(a, b)); return 0; }
編譯:
發(fā)現(xiàn)報錯: 這是因為gcc編譯時去鏈接庫和頭文件,是去默認(rèn)路徑以及當(dāng)前源文件路徑下尋找;
//gcc 尋找的默認(rèn)頭文件路徑:不建議污染原生庫 /usr/include //gcc 尋找的默認(rèn)庫文件路徑: /lib , lib64 ......等
而我們將靜態(tài)庫打包到lib目錄下,gcc編譯時就找不到我們的庫了,因此我們編譯的時候,需要指定一些選項,并且?guī)下窂?/lib;
因此,正確鏈接的指令為:
gcc -o test test.c -I ./lib -L ./lib -lmycal -static
- -I(大寫i) + 指定路徑:告知gcc除了默認(rèn)路徑之外,還要去尋找這個指定路徑的頭文件。
- -L + 指定路徑:除默認(rèn)庫路徑以外,需要尋找這個指定路徑的庫
- -l(小寫L)+ 庫名稱:表示要具體鏈接的是哪一個庫;(因為路徑下庫可能不止一個)
- -static 選擇使用靜態(tài)庫的靜態(tài)鏈接
由此,我們就靜態(tài)鏈接生成了一個可執(zhí)行文件test,運(yùn)行test程序結(jié)果如下:
此時我們刪除靜態(tài)庫,發(fā)現(xiàn)照樣可以運(yùn)行,因為靜態(tài)庫中Add和Sub的二進(jìn)制代碼已經(jīng)被鏈接入test程序中了,不怕原生庫沒了!
可見,有時候編譯選項多而雜,難記,特別是文件一多,寫的很麻煩,介紹一個camke構(gòu)建項目的工具,C/C++開發(fā)人員必會技能;文章鏈接
打包動態(tài)庫
類似與打包靜態(tài)庫使用的ar歸檔工具,動態(tài)庫也有自己的語法,我們將生成動態(tài)庫的依賴關(guān)系及方法寫進(jìn)自動化構(gòu)建工具(Makefile)中::
顯然手動寫Makefile和上面手動打包靜態(tài)庫一樣,麻煩很多,我的評價是,直接cmake起飛;
注意:
- 由于動態(tài)庫在內(nèi)存中是可加載的,它可能在內(nèi)存中的任意位置,也可能被映射到進(jìn)程地址空間的每個區(qū)域,所以為了保證庫當(dāng)中的代碼執(zhí)行不會出錯,也就是要保證庫中的代碼是與位置無關(guān)的,因此生成.o文件時需要帶上-fPIC選項表示生成與位置無關(guān)碼。
- 這里由于在依賴關(guān)系中已經(jīng)點明了要生成的目標(biāo)文件,故不帶上$@也可以
- 打包動態(tài)庫不是像靜態(tài)庫一樣先gcc -o再使用ar(歸檔工具);
- 而是用gcc 帶上-shared選項表示生成共享動態(tài)庫格式,這也體現(xiàn)了動態(tài)庫代碼映射在共享區(qū)的特點
編寫好Makefile之后 make指令構(gòu)建動態(tài)庫 libmycal.so:
使用動態(tài)庫
和靜態(tài)庫一樣,我們把頭文件和.so庫文件放入lib目錄,gcc的時候帶上選項;
gcc -o test test.c -I ./lib -L ./lib -l mycal //因為是動態(tài)鏈接 所以不用帶-static了
然后編譯過了,運(yùn)行程序時發(fā)現(xiàn)有問題,打不開動態(tài)庫?:
既然編譯都聲稱可執(zhí)行程序了,此時的可執(zhí)行程序是沒問題的,因此已經(jīng)與編譯過程無關(guān)了;
那么這屬于運(yùn)行問題,其實運(yùn)行時系統(tǒng)也會去默認(rèn)路徑下找到我們所使用的動態(tài)庫,但在默認(rèn)路徑下沒有我們的庫。
這里解決方法有多種,但我傾向于推薦下面這一種:
修改環(huán)境變量LD_LIBRARY_PATH
,將動態(tài)庫所在路徑.lib添加到該環(huán)境變量中,這樣程序在運(yùn)行時系統(tǒng)就能夠找到動態(tài)庫,從而運(yùn)行成功。
當(dāng)然,還可以拷貝我們的.so文件到系統(tǒng)共享庫路徑下, 一般指/usr/lib;
但是這可能會污染系統(tǒng)原生的庫,一般不推薦這樣做。
還有一種方法,在我們的系統(tǒng)下有**/etc/ld.so.conf.d/**這個路徑:
我們可以在這個路徑下制造自己的.conf,然后再將自己的庫路徑寫進(jìn)這個conf中;
但是還是有點污染了原生庫,不建議;
小結(jié)
linux打包使用靜態(tài)庫:
接口的.c源文件–>.o目標(biāo)二進(jìn)制文件–>ar rc(歸檔工具)進(jìn)行打包成.a靜態(tài)庫,編譯程序使用時帶上-static;(注意帶上頭文件尋找路徑選項)
linux打包使用動態(tài)庫:
接口的.c源文件–>.o目標(biāo)二進(jìn)制文件(需帶上-fPIC 與位置無關(guān))–>-shard 打包動態(tài)庫;(注意帶上頭文件尋找路徑選項)+(注意添加動態(tài)庫尋找路徑)
win下打包動靜態(tài)庫
比如通過VS2019打包,由于是可視化界面方便操作,不再贅述,參考下方文章;
總結(jié)
到此這篇關(guān)于Linux下動靜態(tài)庫的打包與使用指南(C/C++)的文章就介紹到這了,更多相關(guān)Linux動靜態(tài)庫打包使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
CFileDialog的鉤子函數(shù)解決對話框的多選之DoModal問題
前幾天領(lǐng)導(dǎo)問我一個問題:就是使用CFileDialog類在設(shè)置多選時選中的文件所放的文件緩沖區(qū)不知設(shè)置多大合適,本文將詳細(xì)介紹,需要的朋友可以參考下2012-12-12stl常用算法(Algorithms)介紹(stl排序算法、非變序型隊列)
這篇文章主要介紹了stl常用算法(Algorithms)介紹(stl排序算法、非變序型隊列),需要的朋友可以參考下2014-05-05