兩個要知道的基本知識html
Linux 應用程序由於 Linux 版本的衆多與各自獨立性,在工程製做與使用中必須熟練掌握以下兩點纔能有效地工做和理想地運行。linux
爲了演示三種不一樣的標準庫連接方式對最終應用程序產生的區別, 這裏用了一個經典的示例應用程序 HelloWorld 作演示,見 清單 1 HelloWorld。 整個工程能夠在文章末尾下載。c++
#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
標準庫鏈接方式 | 示例鏈接選項 | 優勢 | 缺點 |
---|---|---|---|
全靜態 | -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
經過上述三圖,能夠清楚的看到,當用 全靜態標準庫的連接方式時,所生成的可執行文件最終不依賴任何的動態標準庫,
而 全動態標準庫的連接方式會致使最終應用程序可執行文件依賴於全部用到的標準動態庫。
區別於上述兩種方式的 半靜態連接方式則有針對性的將 libgcc 和 libstdc++ 兩個標準庫非動態連接。
(對比 圖 2與 圖 3,可見在 圖 3中這兩個標準庫的動態依賴不見了)
從實際應用當中發現,最理想的標準庫連接方式就是半靜態連接,一般會選擇將 libgcc 與 libstdc++ 這兩個標準庫靜態連接,
從而避免應用程序在不一樣 Linux 版本間標準庫依賴不兼容的問題發生。
維度二 : 最終生成的可執行文件大小(使用 size 命令進行分析)
size 簡介:該命令用於顯示出可執行文件的大小
涉及語法:size objfile...
其餘詳細說明請參閱 man 說明。
三種標準庫連接方式最終產生的應用程序的可執行文件的大小具體差別見 圖 4、圖 5、圖 6所示:
經過上述三圖能夠看出,最終可執行文件的大小隨最終所依賴的標準動態庫的數量增長而減少。
從實際應用當中發現,最理想的是 半靜態連接方式,由於該方式可以在避免應用程序於
不一樣 Linux 版本間標準庫依賴不兼容的問題發生的同時,使最終生成的可執行文件大小最小化。
-llibrary
-l library:指定所須要的額外庫
-Ldir:指定庫搜索路徑
-static:靜態連接全部庫
-static-libgcc:靜態連接 gcc 庫
-static-libstdc++:靜態連接 c++ 庫
關於上述命令的詳細說明,請參閱 GCC 技術手冊
ar 簡介:處理建立、修改、提取靜態庫的操做
涉及選項:
t - 顯示靜態庫的內容
r[ab][f][u] - 更新或增長新文件到靜態庫中
[s] - 建立文檔索引
ar -M [<mri-script] - 使用 ar 腳本處理
其餘詳細說明請參閱 man 說明。
假設現有如 圖 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 下靜態庫的製做。
這樣的 demo.a 當被連接入某個工程時,全部在 TXmlParser.o 和 xmlparser.o 定義的符號都不會被發現,從而會致使連接錯誤,
提示沒法找到對應的符號。顯然,經過圖 8 方式建立 Linux 靜態庫是不正確的。
正確的方式有兩種:
顯然,方式 1 是比較麻煩的,由於涉及到太多的文件處理,可能還要經過不斷建立臨時目錄用於保存中間文件。
推薦使用如 清單 2 createlib.sh所示的 ar 腳本方式進行建立:
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所示的代碼:
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 纔是咱們想要的結果。
正如 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 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 的工程師提供有用的幫助。