庫(kù)與動(dòng)態(tài)庫(kù):從原理到實(shí)戰(zhàn))
1. 什么是庫(kù)在 Linux 編程中庫(kù)Library是一組預(yù)先編譯好的目標(biāo)代碼Object Code的集合它把常用的函數(shù)封裝在一起供其他程序在編譯或運(yùn)行時(shí)調(diào)用。庫(kù)的本質(zhì)是代碼復(fù)用的一種手段它讓開(kāi)發(fā)者不必重復(fù)編寫(xiě)底層功能只需鏈接對(duì)應(yīng)的庫(kù)即可使用其中的函數(shù)。庫(kù)的出現(xiàn)解決了兩個(gè)核心問(wèn)題一是避免重復(fù)編譯相同代碼提高開(kāi)發(fā)效率二是將功能模塊化便于維護(hù)和分發(fā)。例如C 標(biāo)準(zhǔn)庫(kù)中的printf、malloc等函數(shù)就是由系統(tǒng)預(yù)編譯好并封裝在庫(kù)中開(kāi)發(fā)者直接調(diào)用即可。從使用方式上看庫(kù)與普通源碼文件最大的區(qū)別在于源碼需要參與編譯過(guò)程而庫(kù)已經(jīng)是編譯產(chǎn)物只需在鏈接階段被引用。理解這一點(diǎn)是掌握庫(kù)的生成與使用的基礎(chǔ)。2. 庫(kù)的分類(lèi)Linux 下的庫(kù)主要分為兩大類(lèi)靜態(tài)庫(kù)Static Library和動(dòng)態(tài)庫(kù)Dynamic Library也叫共享庫(kù)Shared Library。兩者的核心區(qū)別在于鏈接時(shí)機(jī)和代碼的加載方式。2.1 靜態(tài)庫(kù)靜態(tài)庫(kù)在程序編譯鏈接階段被完整復(fù)制到可執(zhí)行文件中。鏈接完成后可執(zhí)行文件不再依賴(lài)該庫(kù)運(yùn)行時(shí)無(wú)需額外加載。靜態(tài)庫(kù)的文件名通常以lib開(kāi)頭以.a結(jié)尾例如libmylib.a。靜態(tài)庫(kù)的優(yōu)點(diǎn)是可執(zhí)行文件獨(dú)立性強(qiáng)部署簡(jiǎn)單不依賴(lài)目標(biāo)機(jī)器上是否安裝了對(duì)應(yīng)庫(kù)缺點(diǎn)是多個(gè)程序使用同一靜態(tài)庫(kù)時(shí)每個(gè)可執(zhí)行文件都會(huì)包含一份庫(kù)代碼造成磁盤(pán)和內(nèi)存空間的浪費(fèi)且?guī)旄潞笮枰匦戮幾g所有依賴(lài)它的程序。2.2 動(dòng)態(tài)庫(kù)動(dòng)態(tài)庫(kù)在程序運(yùn)行時(shí)才被加載到內(nèi)存中可執(zhí)行文件中只保存對(duì)庫(kù)的引用信息。動(dòng)態(tài)庫(kù)的文件名通常以lib開(kāi)頭以.so結(jié)尾并帶有版本號(hào)例如libmylib.so.1.0。動(dòng)態(tài)庫(kù)的優(yōu)點(diǎn)是可以被多個(gè)進(jìn)程共享節(jié)省內(nèi)存和磁盤(pán)空間庫(kù)更新時(shí)無(wú)需重新編譯調(diào)用程序只需替換庫(kù)文件即可。缺點(diǎn)是程序運(yùn)行時(shí)依賴(lài)目標(biāo)機(jī)器上存在對(duì)應(yīng)版本的庫(kù)如果缺失或版本不匹配程序?qū)o(wú)法啟動(dòng)這也是常見(jiàn)的“庫(kù)找不到”問(wèn)題的根源。對(duì)比項(xiàng)靜態(tài)庫(kù).a動(dòng)態(tài)庫(kù).so鏈接時(shí)機(jī)編譯鏈接階段程序運(yùn)行階段文件命名libxxx.alibxxx.so可執(zhí)行文件體積較大較小運(yùn)行時(shí)依賴(lài)不依賴(lài)庫(kù)文件依賴(lài)庫(kù)文件存在更新維護(hù)需重新編譯程序替換庫(kù)文件即可3. 如何生成庫(kù)下面通過(guò)一個(gè)簡(jiǎn)單的示例演示如何用 C 語(yǔ)言分別生成靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)。假設(shè)我們有一個(gè)工具函數(shù)文件mytool.c內(nèi)容如下int add(int a, int b) { return a b; } int multiply(int a, int b) { return a * b; }對(duì)應(yīng)的頭文件mytool.h聲明這些函數(shù)#ifndef MYTOOL_H #define MYTOOL_H int add(int a, int b); int multiply(int a, int b); #endif3.1 生成靜態(tài)庫(kù)生成靜態(tài)庫(kù)需要兩步先將源文件編譯為目標(biāo)文件再用ar工具打包成庫(kù)。# 第一步編譯為目標(biāo)文件 gcc -c mytool.c -o mytool.o # 第二步用 ar 打包成靜態(tài)庫(kù) ar rcs libmytool.a mytool.o其中ar的參數(shù)含義為r表示插入或替換文件c表示創(chuàng)建庫(kù)s表示生成索引。生成后可用ar -t libmytool.a查看庫(kù)中包含的目標(biāo)文件。3.2 生成動(dòng)態(tài)庫(kù)生成動(dòng)態(tài)庫(kù)使用gcc -shared選項(xiàng)并建議加上-fPIC生成位置無(wú)關(guān)代碼以便庫(kù)被多個(gè)進(jìn)程共享加載。# 編譯并生成動(dòng)態(tài)庫(kù) gcc -fPIC -shared mytool.c -o libmytool.so如果需要帶版本號(hào)可以這樣命名gcc -fPIC -shared mytool.c -o libmytool.so.1.03.3 使用庫(kù)編譯程序編寫(xiě)主程序main.c調(diào)用庫(kù)中的函數(shù)#include stdio.h #include mytool.h int main() { printf(3 5 %d\n, add(3, 5)); printf(3 * 5 %d\n, multiply(3, 5)); return 0; }鏈接靜態(tài)庫(kù)編譯gcc main.c -L. -lmytool -o app_static鏈接動(dòng)態(tài)庫(kù)編譯gcc main.c -L. -lmytool -o app_dynamic其中-L.指定庫(kù)搜索路徑為當(dāng)前目錄-lmytool表示鏈接名為libmytool的庫(kù)。注意鏈接動(dòng)態(tài)庫(kù)時(shí)如果系統(tǒng)找不到庫(kù)文件需要先設(shè)置環(huán)境變量LD_LIBRARY_PATH或更新ldconfig配置。4. 易錯(cuò)點(diǎn)在實(shí)際開(kāi)發(fā)中靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)的使用經(jīng)常遇到各種問(wèn)題下面總結(jié)幾個(gè)最常見(jiàn)的坑。4.1 鏈接時(shí)找不到庫(kù)編譯時(shí)提示cannot find -lmytool通常是因?yàn)閹?kù)文件不在默認(rèn)搜索路徑中且沒(méi)有用-L指定路徑。解決辦法是確認(rèn)庫(kù)文件確實(shí)存在并在編譯命令中加上-L指定庫(kù)所在目錄。4.2 運(yùn)行時(shí)找不到動(dòng)態(tài)庫(kù)程序編譯成功但運(yùn)行時(shí)提示error while loading shared libraries: libmytool.so: cannot open shared object file。這是因?yàn)閯?dòng)態(tài)庫(kù)在運(yùn)行時(shí)需要被系統(tǒng)找到而默認(rèn)搜索路徑不包含當(dāng)前目錄。解決辦法有兩種# 方法一臨時(shí)設(shè)置環(huán)境變量 export LD_LIBRARY_PATH.:$LD_LIBRARY_PATH ./app_dynamic 方法二將庫(kù)路徑寫(xiě)入系統(tǒng)配置 sudo ldconfig /path/to/lib4.3 忘記加 -fPIC生成動(dòng)態(tài)庫(kù)時(shí)如果忘記加-fPIC在 64 位系統(tǒng)上鏈接時(shí)可能報(bào)錯(cuò)relocation R_X86_64_32 against ... can not be used when making a shared object。解決辦法是編譯動(dòng)態(tài)庫(kù)時(shí)始終加上-fPIC選項(xiàng)。4.4 靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)同名沖突當(dāng)目錄下同時(shí)存在libmytool.a和libmytool.so時(shí)編譯器默認(rèn)優(yōu)先鏈接動(dòng)態(tài)庫(kù)。如果希望強(qiáng)制使用靜態(tài)庫(kù)可以加上-static選項(xiàng)或直接指定庫(kù)文件的完整路徑。# 強(qiáng)制使用靜態(tài)庫(kù) gcc main.c -L. -static -lmytool -o app_static_force4.5 頭文件與庫(kù)版本不匹配頭文件聲明的函數(shù)簽名與庫(kù)中實(shí)際實(shí)現(xiàn)不一致會(huì)導(dǎo)致編譯通過(guò)但鏈接失敗或運(yùn)行時(shí)出現(xiàn)段錯(cuò)誤。解決辦法是確保頭文件與庫(kù)文件來(lái)自同一版本修改接口后要同步重新編譯庫(kù)和調(diào)用程序。掌握以上要點(diǎn)就能在 Linux 下熟練地創(chuàng)建和使用靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)避免常見(jiàn)的鏈接和運(yùn)行錯(cuò)誤。5. 總結(jié)本文圍繞 Linux 編程中的靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)展開(kāi)先介紹了庫(kù)的概念與作用再對(duì)比了兩類(lèi)庫(kù)在鏈接時(shí)機(jī)、文件命名、體積、運(yùn)行時(shí)依賴(lài)和更新維護(hù)上的差異隨后通過(guò)具體命令演示了如何用gcc和ar生成并鏈接靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)最后總結(jié)了鏈接失敗、運(yùn)行時(shí)找不到庫(kù)、忘記-fPIC、同名沖突以及頭文件與庫(kù)版本不匹配等常見(jiàn)問(wèn)題。在實(shí)際開(kāi)發(fā)中建議優(yōu)先根據(jù)部署環(huán)境和更新頻率選擇合適的庫(kù)類(lèi)型追求獨(dú)立部署可選靜態(tài)庫(kù)追求節(jié)省空間和靈活更新可選動(dòng)態(tài)庫(kù)。同時(shí)生成動(dòng)態(tài)庫(kù)時(shí)務(wù)必加上-fPIC運(yùn)行時(shí)注意通過(guò)LD_LIBRARY_PATH或ldconfig配置庫(kù)搜索路徑并保持頭文件與庫(kù)版本一致這樣才能減少鏈接和運(yùn)行階段的各類(lèi)錯(cuò)誤。