技巧:Linux 動態庫與靜態庫製做及使用詳解

兩個要知道的基本知識html

Linux 應用程序由於 Linux 版本的衆多與各自獨立性,在工程製做與使用中必須熟練掌握以下兩點纔能有效地工做和理想地運行。linux

  1. Linux 下標準庫連接的三種方式(全靜態 , 半靜態 (libgcc,libstdc++), 全動態)及其各自利弊。

  2. Linux 下如何巧妙構建 achrive(*.a),而且如何設置連接選項來解決 gcc 比較特別的連接庫的順序問題。

三種標準庫連接方式選項及對比ios

爲了演示三種不一樣的標準庫連接方式對最終應用程序產生的區別, 這裏用了一個經典的示例應用程序 HelloWorld 作演示,見 清單 1 HelloWorld。 整個工程能夠在文章末尾下載。c++


清單 1. HelloWorld
#include <stdio.h> 
 #include <iostream> 
 using std::cout; 
 using std::endl; 


 int main(int argc, char* argv[]) 
 { 
  printf("HelloWorld!(Printed by printf)\n"); 

  cout<<"HelloWorld!(Printed by cout)"<<endl; 

  return 0; 
 }

三種標準庫連接方式的選項及區別見 表 1this


表 1. 三種標準庫連接方式的選項及區別
標準庫鏈接方式 示例鏈接選項 優勢 缺點
全靜態 -static -pthread -lrt -ldl 不會發生應用程序在 不一樣 Linux 版本下的標準庫不兼容問題。 生成的文件比較大,
應用程序功能受限(不能調用動態庫等)
全動態 -pthread -lrt -ldl 生成文件是三者中最小的 比較容易發生應用程序在 
不一樣 Linux 版本下標準庫依賴不兼容問題。
半靜態 (libgcc,libstdc++) -static-libgcc -L. -pthread -lrt -ldl 靈活度大,可以針對不一樣的標準庫採起不一樣的連接策略,
從而避免不兼容問題發生。
結合了全靜態與全動態兩種連接方式的優勢。
比較難識別哪些庫容易發生不兼容問題,
目前只有依靠經驗積累。
某些功能會因選擇的標準庫版本而喪失。

上述三種標準庫連接方式中,比較特殊的是 半靜態連接方式,主要在於其還須要在連接前增長額外的一個步驟:
ln -s `g++ -print-file-name=libstdc++.a`,做用是將 libstdc++.a(libstdc++ 的靜態庫)符號連接到本地工程連接目錄。
-print-file-name 在 gcc 中的解釋以下:
-print-file-name=<lib> Display the full path to library <lib>spa

爲了區分三種不一樣的標準庫連接方式對最終生成的可執行文件的影響,本文從兩個不一樣的維度進行分析比較:.net

維度一:最終生成的可執行文件對標準庫的依賴方式(使用 ldd 命令進行分析)code

ldd 簡介:該命令用於打印出某個應用程序或者動態庫所依賴的動態庫 
涉及語法:ldd [OPTION]... FILE...
其餘詳細說明請參閱 man 說明。orm

三種標準庫連接方式最終產生的應用程序的可執行文件對於標準庫的依賴方式具體差別見 圖 1圖 2圖 3所示:xml


圖 1. 全靜態標準庫連接方式
全靜態標準庫連接方式  

圖 2. 全動態標準庫連接方式
全動態標準庫連接方式  

圖 3. 半靜態(libgcc,libstdc++) 標準庫連接方式
半靜態(libgcc,libstdc++) 標準庫連接方式  


經過上述三圖,能夠清楚的看到,當用 全靜態標準庫的連接方式時,所生成的可執行文件最終不依賴任何的動態標準庫,
而 全動態標準庫的連接方式會致使最終應用程序可執行文件依賴於全部用到的標準動態庫。
區別於上述兩種方式的 半靜態連接方式則有針對性的將 libgcc 和 libstdc++ 兩個標準庫非動態連接。
(對比 圖 2與 圖 3,可見在 圖 3中這兩個標準庫的動態依賴不見了)

從實際應用當中發現,最理想的標準庫連接方式就是半靜態連接,一般會選擇將 libgcc 與 libstdc++ 這兩個標準庫靜態連接,
從而避免應用程序在不一樣 Linux 版本間標準庫依賴不兼容的問題發生。

維度二 : 最終生成的可執行文件大小(使用 size 命令進行分析)

size 簡介:該命令用於顯示出可執行文件的大小 
涉及語法:size objfile...
其餘詳細說明請參閱 man 說明。

三種標準庫連接方式最終產生的應用程序的可執行文件的大小具體差別見 圖 4圖 5圖 6所示:


圖 4. 全靜態標準庫連接方式
全靜態標準庫連接方式  

圖 5. 全動態標準庫連接方式
全動態標準庫連接方式  

圖 6. 半靜態(libgcc,libstdc++) 標準庫連接方式
半靜態(libgcc,libstdc++) 標準庫連接方式  


經過上述三圖能夠看出,最終可執行文件的大小隨最終所依賴的標準動態庫的數量增長而減少。
從實際應用當中發現,最理想的是 半靜態連接方式,由於該方式可以在避免應用程序於 
不一樣 Linux 版本間標準庫依賴不兼容的問題發生的同時,使最終生成的可執行文件大小最小化。

示例連接選項中所涉及命令(引用 GCC 原文):

-llibrary
-l library:指定所須要的額外庫 
-Ldir:指定庫搜索路徑 
-static:靜態連接全部庫 
-static-libgcc:靜態連接 gcc 庫 
-static-libstdc++:靜態連接 c++ 庫 
關於上述命令的詳細說明,請參閱 GCC 技術手冊 

回頁首

Linux 下靜態庫(archive)的製做方式:

涉及命令:ar

ar 簡介:處理建立、修改、提取靜態庫的操做 

涉及選項:
t - 顯示靜態庫的內容 
r[ab][f][u] - 更新或增長新文件到靜態庫中 
[s] - 建立文檔索引 
ar -M [<mri-script] - 使用 ar 腳本處理 
其餘詳細說明請參閱 man 說明。

示例情景:

假設現有如 圖 7所示兩個庫文件


圖 7. 示例靜態庫文件
示例靜態庫文件  

從 圖 7中能夠得知,CdtLog.a 只包含 CdtLog.o 一個對象文件 , 而 xml.a 包含 TXmlParser.o 和 xmlparser.o 兩個對象文件 
現將 CdtLog.o 提取出來,而後經過 圖 8方式建立一個新的靜態庫 demo.a,能夠看出,demo.a 包含的是 CdtLog.o 以及 xml.a,
而不是咱們所預期的 CdtLog.o,TXmlParser.o 和 xmlparser.o。這正是區別於 Windows 下靜態庫的製做。


圖 8. 示例靜態庫製做方式 1
示例靜態庫製做方式 1  

這樣的 demo.a 當被連接入某個工程時,全部在 TXmlParser.o 和 xmlparser.o 定義的符號都不會被發現,從而會致使連接錯誤,
提示沒法找到對應的符號。顯然,經過圖 8 方式建立 Linux 靜態庫是不正確的。

正確的方式有兩種:

  1. 將全部靜態庫中包含的對象文件提取出來而後從新打包成新的靜態庫文件。

  2. 用一種更加靈活的方式建立新的靜態庫文件:ar 腳本

顯然,方式 1 是比較麻煩的,由於涉及到太多的文件處理,可能還要經過不斷建立臨時目錄用於保存中間文件。
推薦使用如 清單 2 createlib.sh所示的 ar 腳本方式進行建立:


清單 2 createlib.sh
rm demo.a 
 rm ar.mac 
 echo CREATE demo.a > ar.mac 
 echo SAVE >> ar.mac 
 echo END >> ar.mac 
 ar -M < ar.mac 
 ar -q demo.a CdtLog.o 
 echo OPEN demo.a > ar.mac 
 echo ADDLIB xml.a >> ar.mac 
 echo SAVE >> ar.mac 
 echo END >> ar.mac 
 ar -M < ar.mac 
 rm ar.mac

若是想在 Linux makefile 中使用 ar 腳本方式進行靜態庫的建立,能夠編寫如 清單 3 BUILD_LIBRARY所示的代碼:


清單 3 BUILD_LIBRARY
define BUILD_LIBRARY 
 $(if $(wildcard $@),@$(RM) $@) 
 $(if $(wildcard ar.mac),@$(RM) ar.mac) 
 $(if $(filter %.a, $^), 
 @echo CREATE $@ > ar.mac 
 @echo SAVE >> ar.mac 
 @echo END >> ar.mac 
 @$(AR) -M < ar.mac 
 ) 
 $(if $(filter %.o,$^),@$(AR) -q $@ $(filter %.o, $^)) 
 $(if $(filter %.a, $^), 
 @echo OPEN $@ > ar.mac 
 $(foreach LIB, $(filter %.a, $^), 
 @echo ADDLIB $(LIB) >> ar.mac 
 ) 
 @echo SAVE >> ar.mac 
 @echo END >> ar.mac 
 @$(AR) -M < ar.mac 
 @$(RM) ar.mac 
 ) 
 endef 

 $(TargetDir)/$(TargetFileName):$(OBJS) 
    $(BUILD_LIBRARY)

經過 圖 9,咱們能夠看到,用這種方式產生的 demo.a 纔是咱們想要的結果。


圖 9. 巧妙建立的靜態庫文件結果
巧妙建立的靜態庫文件結果  

回頁首

Linux 靜態庫連接順序問題及解決方法:

正如 GCC 手冊中提到的那樣:
It makes a difference where in the command you write this option; the linker
searches and processes libraries and object files in the order they are specified.
Thus, ‘ foo.o -lz bar.o ’ searches library ‘ z ’ after file ‘ foo.o ’ but before
‘ bar.o ’ . If ‘ bar.o ’ refers to functions in ‘ z ’ , those functions may not be loaded.

爲了解決這種庫連接順序問題,咱們須要增長一些連接選項 :

$(CXX) $(LINKFLAGS) $(OBJS) -Xlinker "-(" $(LIBS) -Xlinker "-)" -o $@

經過將全部須要被連接的靜態庫放入 -Xlinker "-(" 與 -Xlinker "-)" 之間,能夠是 g++ 連接過程當中, 自動循環連接全部靜態庫,從而解決了本來的連接順序問題。

涉及連接選項:-Xlinker

-Xlinker option
Pass option as an option to the linker. You can use this to supply system-specific
linker options which GCC does not know how to recognize.

回頁首

小結

本文介紹了 Linux 下三種標準庫連接的方式及各自利弊,同時還介紹了 Linux 下靜態庫的製做及使用方法,相信可以給 大多數須要部署 Linux 應用程序和編寫 Linux Makefile 的工程師提供有用的幫助。

相關文章
相關標籤/搜索