首先 我們要明白什么是庫?
庫(Library)是一組預編譯的代碼,提供特定的功能,可以被多個程序共享調用,避免重復編寫代碼。在鏈接步驟中,鏈接器將從庫文件取得所需的代碼,復制到生成的可執行文件中
Linux 中常見的庫文件有兩種,一種.a為后綴,為靜態庫,另一種以.so為后綴,為動態庫。
靜態庫:在程序編譯時,將庫的代碼“拷貝”到最終的可執行文件中;
動態庫(共享庫):在程序運行時,動態載入庫的代碼,多個程序可以共享同一份庫文件。
靜態庫
靜態庫通常以.a為后綴(在Linux下),是多個目標文件(.o)組成的歸檔文件(Archive),里面存儲著一組目標文件。
特點
- 在鏈接(Linking)的時候被合并到可執行文件中
- 可執行文件較大,因為庫的代碼已經復制到程序中
- 加載快,無需在運行時再載入庫文件
- 無需在運行環境中存在庫文件,便于部署
作用
- 方便將多個目標文件打包成一個完整的可執行程序
- 使用簡單,不依賴外部共享庫
創建靜態庫
# 先編譯源文件生成目標文件
gcc -c file1.c -o file1.o
gcc -c file2.c -o file2.o# 將目標文件打包為靜態庫
ar rcs libmylib.a file1.o file2.o# 編譯時鏈接靜態庫
gcc main.o -L. -lmylib -o myprogram
使用靜態庫
- 在編譯時指定庫路徑和庫名(-L和-l參數)
- l:指定庫名(庫的文件名為 libxxx.a,庫名為 xxx)
- L:指定庫路徑
- static:使用靜態鏈接
gcc -static main.c -o main -l math -L ./
// 錯誤:gcc -l math -static main.c -o main -L ./
特別注意,必須把 -l math 放在后面。放在最后時它是這樣的一個解析過程:
- 鏈接器從左往右掃描可重定位目標文件和靜態庫
- 掃描 main.c 時,發現兩個未解析的符號 add 和 sub,記住這兩個未解析的符號
- 掃描 libmath.a,找到了前面未解析的符號,因此提取相關代碼
- 最終沒有任何未解析的符號,編譯鏈接完成
那如果將 -l math 放在前面,又是怎樣的情況呢? - 鏈接器從左往右掃描可重定位目標文件和靜態庫
- 掃描 libmath.a,由于前面沒有任何未解析的符號,因此不會提取任何代碼
- 掃描 main.c,發現未解析的符號 add 和 sub
- 掃描結束,還有兩個未解析的符號,因此編譯鏈接報錯
如果把-l math放在前面,編譯結果如下:
我們看看最終生成的文件大小:
生成可執行文件大小為892k
由于最終生成的可執行文件中已經包含了add和sub相關的二進制代碼,因此這個可執行文件在一個沒有libmath.a的Linux系統中也能正常運行。
動態庫
動態庫和靜態庫類似,但是它并不在鏈接時將需要的二進制代碼都“拷貝”到可執行文件中,而是僅僅“拷貝”一些重定位和符號表信息,這些信息可以在程序運行時完成真正的鏈接過程。Linux 中這類庫的名字一般是 libxxx.so。(shared object)
符號表
- 符號表記錄了函數和變量的名字、地址等信息,是連接器、加載器用來解決引用關系的重要數據結構。
- 在動態庫中,符號表包括:
- 導出符號:庫中提供給外部調用的函數和變量
- 需要被其他程序或庫引用的符號
重定位信息
- 重定位是指在程序或庫加載到內存后,動態調整代碼中的地址引用,使得符號指向正確的內存地址。
- 重定位信息記錄了哪些位置需要修正(如函數調用、全局變量訪問位置)
- 在靜態鏈接中,重定位在鏈接階段完成;在動態鏈接中,加載時由系統完成。
定義:
- 動態庫也稱“共享庫”,以.so(Shared Object)為后綴
- 在程序運行時動態載入,多個程序可以共享同一份庫文件
特點
- 在程序加載時才載入庫(動態鏈接)
- 可減小可執行文件大小,因為庫在運行時加載
- 便于維護和升級,只需替換庫文件,無需重新編譯所有程序
- 運行時依賴,程序必須找到對應的.so文件
作用
- 方便庫的共享和維護
- 可以實現插件式開發或模塊化設計
創建動態庫
# 編譯生成共享庫
gcc -fPIC -c file1.c -o file1.o
gcc -fPIC -c file2.c -o file2.o
gcc -shared -o libmylib.so file1.o file2.o# 編譯程序鏈接動態庫
gcc main.o -L. -lmylib -o myprogram
注意:
- -fPIC:生成位置無關代碼(Position Independent Code)
- -shared:生成共享庫
使用動態庫
步驟 | 命令示例 | 說明 |
---|---|---|
1. 創建共享庫 | gcc -fPIC -c mylib.c -o mylib.o gcc -shared -o libmylib.so mylib.o | 生成.so 文件 |
2. 編譯調用程序 | gcc main.c -L. -lmylib | 連接到共享庫 |
3. 設置環境變量或放庫到系統路徑 | export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH sudo cp libmylib.so /usr/lib/ | 讓運行時找到共享庫 |
4. 運行程序 | ./myprogram | 運行程序,調用共享庫中的函數 |
找不到的原因
原因類別 | 描述 | 解決措施 |
---|---|---|
庫路徑未在標準路徑或未指定 | 庫文件所在路徑未加入系統路徑或未在運行時指定 | 設置LD_LIBRARY_PATH 或rpath |
缺少ldconfig 刷新緩存 | 添加新庫后未刷新系統緩存 | 運行ldconfig |
編譯參數不正確 | 編譯時未設置-rpath 或路徑錯誤 | 使用-Wl,-rpath 指定路徑 |
依賴缺失 | 庫依賴的其他庫不存在或路徑錯誤 | 用ldd 檢測,安裝缺失的庫 |
權限不足 | 庫文件權限限制 | 修改文件權限 |
主要原因是系統在運行時無法定位到庫文件的存放路徑,可能因為庫路徑沒有配置正確、庫文件放錯位置、未刷新緩存或依賴缺失等。
當在運行程序時,動態庫(.so文件)找不到,系統會報錯,導致程序無法正常啟動。這種情況常見的錯誤信息有:
error while loading shared libraries: libmylib.so: cannot open shared object file: No such file or directory
動態庫找不到時的解決辦法
1.臨時設置環境變量LD_LIBRARY_PATH
這是一種快速方便的方式,臨時告訴系統去哪里找共享庫。
export LD_LIBRARY_PATH=.:/path/to/lib:$LD_LIBRARY_PATH
./your_program
示例:假設libmylib.so在當前目錄:
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./your_program
缺點:此設置只在當前終端有效,關閉終端后失效。
2.將共享庫拷貝到系統標準目錄
將你的共享庫復制到系統的庫目錄(如/usr/lib或/usr/local/lib),然后刷新緩存。
sudo cp libmylib.so /usr/lib/
sudo ldconfig
說明:
ldconfig命令會刷新系統的庫緩存,讓系統知道新加入的庫。
注意:需要管理員權限。
3.修改程序的RPATH或RUNPATH(在編譯時指定)
在編譯時,將庫的路徑提前嵌入到可執行文件中,使其在運行時自動查找。
例:
gcc main.c -L/path/to/lib -Wl,-rpath=/path/to/lib -lmylib -o myprogram
- -Wl,-rpath=路徑:在可執行文件中指定庫搜索路徑
執行后,無需設置LD_LIBRARY_PATH,系統會自動在指定路徑查找庫。
4.使用LD_LIBRARY_PATH環境變量文件(配置全局)
編輯/etc/ld.so.conf或在/etc/ld.so.conf.d/目錄中添加自定義路徑,然后運行
sudo ldconfig
這樣,系統會在指定路徑中查找庫。
5. 動態調試:使用ldd命令檢測缺失的依賴庫
運行:
ldd ./your_program
可以顯示程序依賴的所有共享庫,查看是否有“not found”的庫。
如果發現缺失的庫,可以用上面的方法解決。
小結
方法 | 作用 | 適用場景 |
---|---|---|
LD_LIBRARY_PATH | 臨時指定庫路徑 | 調試或快速測試 |
拷貝到系統目錄 | 將庫放到標準路徑,系統自動加載 | 部署時,保證程序可以找到庫 |
RPATH/RUNPATH | 在編譯時嵌入路徑 | 內部控制,避免環境變量設置 |
ldconfig 配置 | 全系統范圍配置庫搜索路徑 | 長期維護或系統級別的配置 |
ldd 命令檢測依賴 | 查看依賴關系,找出缺失的庫 | 調試、排錯 |
靜態庫與動態庫的區別
特性 | 靜態庫(.a ) | 動態庫(.so ) |
---|---|---|
鏈接時間 | 編譯時(靜態鏈接) | 運行時(動態鏈接) |
生成文件大小 | 較大(所用到代碼要從二進制文件中拷貝一份,復制到可執行文件中) | 較小(庫文件單獨存在,僅僅復制了一些重定位和符號表信息) |
占用空間 | 運行時每個程序都持有一份庫的副本 | 多個程序共享同一份庫(內存節省) |
升級維護 | 需要重新編譯程序 | 只需替換庫文件,無需重新編譯 |
依賴關系 | 不需要庫文件存在于運行環境中 | 需要庫文件存在于運行環境中 |
運行速度 | 略快(沒有動態加載耗時) | 因為需要動態加載,可能略慢 |