|
兩個(gè)要知道的基本知識(shí)
Linux 應(yīng)用程序因?yàn)?Linux 版本的眾多與各自獨(dú)立性,在工程制作與使用中必須熟練掌握如下兩點(diǎn)才能有效地工作和理想地運(yùn)行。
-
Linux 下標(biāo)準(zhǔn)庫鏈接的三種方式(全靜態(tài) , 半靜態(tài) (libgcc,libstdc++), 全動(dòng)態(tài))及其各自利弊。
-
Linux 下如何巧妙構(gòu)建 achrive(*.a),并且如何設(shè)置鏈接選項(xiàng)來解決 gcc 比較特別的鏈接庫的順序問題。
三種標(biāo)準(zhǔn)庫鏈接方式選項(xiàng)及對比
為了演示三種不同的標(biāo)準(zhǔn)庫鏈接方式對最終應(yīng)用程序產(chǎn)生的區(qū)別,
這里用了一個(gè)經(jīng)典的示例應(yīng)用程序 HelloWorld 做演示,見 清單 1 HelloWorld。
整個(gè)工程可以在文章末尾下載。
清單 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;
}
|
三種標(biāo)準(zhǔn)庫鏈接方式的選項(xiàng)及區(qū)別見 表 1
表 1. 三種標(biāo)準(zhǔn)庫鏈接方式的選項(xiàng)及區(qū)別
| 標(biāo)準(zhǔn)庫連接方式 | 示例連接選項(xiàng) | 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|
| 全靜態(tài) | -static -pthread -lrt -ldl | 不會(huì)發(fā)生應(yīng)用程序在
不同 Linux 版本下的標(biāo)準(zhǔn)庫不兼容問題。 | 生成的文件比較大,
應(yīng)用程序功能受限(不能調(diào)用動(dòng)態(tài)庫等) |
|---|
| 全動(dòng)態(tài) | -pthread -lrt -ldl | 生成文件是三者中最小的 | 比較容易發(fā)生應(yīng)用程序在
不同 Linux 版本下標(biāo)準(zhǔn)庫依賴不兼容問題。 |
|---|
| 半靜態(tài) (libgcc,libstdc++) | -static-libgcc -L. -pthread -lrt -ldl | 靈活度大,能夠針對不同的標(biāo)準(zhǔn)庫采取不同的鏈接策略,
從而避免不兼容問題發(fā)生。
結(jié)合了全靜態(tài)與全動(dòng)態(tài)兩種鏈接方式的優(yōu)點(diǎn)。 | 比較難識(shí)別哪些庫容易發(fā)生不兼容問題,
目前只有依靠經(jīng)驗(yàn)積累。
某些功能會(huì)因選擇的標(biāo)準(zhǔn)庫版本而喪失。 |
|---|
上述三種標(biāo)準(zhǔn)庫鏈接方式中,比較特殊的是 半靜態(tài)鏈接方式,主要在于其還需要在鏈接前增加額外的一個(gè)步驟:
ln -s `g++ -print-file-name=libstdc++.a`,作用是將 libstdc++.a(libstdc++ 的靜態(tài)庫)符號(hào)鏈接到本地工程鏈接目錄。
-print-file-name 在 gcc 中的解釋如下:
-print-file-name=<lib> Display the full path to library <lib>
為了區(qū)分三種不同的標(biāo)準(zhǔn)庫鏈接方式對最終生成的可執(zhí)行文件的影響,本文從兩個(gè)不同的維度進(jìn)行分析比較:
維度一:最終生成的可執(zhí)行文件對標(biāo)準(zhǔn)庫的依賴方式(使用 ldd 命令進(jìn)行分析)
ldd 簡介:該命令用于打印出某個(gè)應(yīng)用程序或者動(dòng)態(tài)庫所依賴的動(dòng)態(tài)庫
涉及語法:ldd [OPTION]... FILE...
其他詳細(xì)說明請參閱 man 說明。
三種標(biāo)準(zhǔn)庫鏈接方式最終產(chǎn)生的應(yīng)用程序的可執(zhí)行文件對于標(biāo)準(zhǔn)庫的依賴方式具體差異見 圖 1、圖 2、圖 3所示:
圖 1. 全靜態(tài)標(biāo)準(zhǔn)庫鏈接方式
圖 2. 全動(dòng)態(tài)標(biāo)準(zhǔn)庫鏈接方式
圖 3. 半靜態(tài)(libgcc,libstdc++) 標(biāo)準(zhǔn)庫鏈接方式
通過上述三圖,可以清楚的看到,當(dāng)用 全靜態(tài)標(biāo)準(zhǔn)庫的鏈接方式時(shí),所生成的可執(zhí)行文件最終不依賴任何的動(dòng)態(tài)標(biāo)準(zhǔn)庫,
而 全動(dòng)態(tài)標(biāo)準(zhǔn)庫的鏈接方式會(huì)導(dǎo)致最終應(yīng)用程序可執(zhí)行文件依賴于所有用到的標(biāo)準(zhǔn)動(dòng)態(tài)庫。
區(qū)別于上述兩種方式的 半靜態(tài)鏈接方式則有針對性的將 libgcc 和 libstdc++ 兩個(gè)標(biāo)準(zhǔn)庫非動(dòng)態(tài)鏈接。
(對比 圖 2與 圖 3,可見在 圖 3中這兩個(gè)標(biāo)準(zhǔn)庫的動(dòng)態(tài)依賴不見了)
從實(shí)際應(yīng)用當(dāng)中發(fā)現(xiàn),最理想的標(biāo)準(zhǔn)庫鏈接方式就是半靜態(tài)鏈接,通常會(huì)選擇將 libgcc 與 libstdc++ 這兩個(gè)標(biāo)準(zhǔn)庫靜態(tài)鏈接,
從而避免應(yīng)用程序在不同 Linux 版本間標(biāo)準(zhǔn)庫依賴不兼容的問題發(fā)生。
維度二 : 最終生成的可執(zhí)行文件大?。ㄊ褂?size 命令進(jìn)行分析)
size 簡介:該命令用于顯示出可執(zhí)行文件的大小
涉及語法:size objfile...
其他詳細(xì)說明請參閱 man 說明。
三種標(biāo)準(zhǔn)庫鏈接方式最終產(chǎn)生的應(yīng)用程序的可執(zhí)行文件的大小具體差異見 圖 4、圖 5、圖 6所示:
圖 4. 全靜態(tài)標(biāo)準(zhǔn)庫鏈接方式
圖 5. 全動(dòng)態(tài)標(biāo)準(zhǔn)庫鏈接方式
圖 6. 半靜態(tài)(libgcc,libstdc++) 標(biāo)準(zhǔn)庫鏈接方式
通過上述三圖可以看出,最終可執(zhí)行文件的大小隨最終所依賴的標(biāo)準(zhǔn)動(dòng)態(tài)庫的數(shù)量增加而減小。
從實(shí)際應(yīng)用當(dāng)中發(fā)現(xiàn),最理想的是 半靜態(tài)鏈接方式,因?yàn)樵摲绞侥軌蛟诒苊鈶?yīng)用程序于
不同 Linux 版本間標(biāo)準(zhǔn)庫依賴不兼容的問題發(fā)生的同時(shí),使最終生成的可執(zhí)行文件大小最小化。
示例鏈接選項(xiàng)中所涉及命令(引用 GCC 原文):
-llibrary
-l library:指定所需要的額外庫
-Ldir:指定庫搜索路徑
-static:靜態(tài)鏈接所有庫
-static-libgcc:靜態(tài)鏈接 gcc 庫
-static-libstdc++:靜態(tài)鏈接 c++ 庫
關(guān)于上述命令的詳細(xì)說明,請參閱 GCC 技術(shù)手冊
回頁首 Linux 下靜態(tài)庫(archive)的制作方式:
涉及命令:ar
ar 簡介:處理創(chuàng)建、修改、提取靜態(tài)庫的操作
涉及選項(xiàng):
t - 顯示靜態(tài)庫的內(nèi)容
r[ab][f][u] - 更新或增加新文件到靜態(tài)庫中
[s] - 創(chuàng)建文檔索引
ar -M [<mri-script] - 使用 ar 腳本處理
其他詳細(xì)說明請參閱 man 說明。
示例情景:
假設(shè)現(xiàn)有如 圖 7所示兩個(gè)庫文件
圖 7. 示例靜態(tài)庫文件
從 圖 7中可以得知,CdtLog.a 只包含 CdtLog.o 一個(gè)對象文件 , 而 xml.a 包含 TXmlParser.o 和 xmlparser.o 兩個(gè)對象文件
現(xiàn)將 CdtLog.o 提取出來,然后通過 圖 8方式創(chuàng)建一個(gè)新的靜態(tài)庫 demo.a,可以看出,demo.a 包含的是 CdtLog.o 以及 xml.a,
而不是我們所預(yù)期的 CdtLog.o,TXmlParser.o 和 xmlparser.o。這正是區(qū)別于 Windows 下靜態(tài)庫的制作。
圖 8. 示例靜態(tài)庫制作方式 1
這樣的 demo.a 當(dāng)被鏈接入某個(gè)工程時(shí),所有在 TXmlParser.o 和 xmlparser.o 定義的符號(hào)都不會(huì)被發(fā)現(xiàn),從而會(huì)導(dǎo)致鏈接錯(cuò)誤,
提示無法找到對應(yīng)的符號(hào)。顯然,通過圖 8 方式創(chuàng)建 Linux 靜態(tài)庫是不正確的。
正確的方式有兩種:
-
將所有靜態(tài)庫中包含的對象文件提取出來然后重新打包成新的靜態(tài)庫文件。
-
用一種更加靈活的方式創(chuàng)建新的靜態(tài)庫文件:ar 腳本。
顯然,方式 1 是比較麻煩的,因?yàn)樯婕暗教嗟奈募幚?,可能還要通過不斷創(chuàng)建臨時(shí)目錄用于保存中間文件。
推薦使用如 清單 2 createlib.sh所示的 ar 腳本方式進(jìn)行創(chuàng)建:
清單 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 腳本方式進(jìn)行靜態(tài)庫的創(chuàng)建,可以編寫如 清單 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,我們可以看到,用這種方式產(chǎn)生的 demo.a 才是我們想要的結(jié)果。
圖 9. 巧妙創(chuàng)建的靜態(tài)庫文件結(jié)果
回頁首 Linux 靜態(tài)庫鏈接順序問題及解決方法:
正如 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.
為了解決這種庫鏈接順序問題,我們需要增加一些鏈接選項(xiàng) :
$(CXX) $(LINKFLAGS) $(OBJS) -Xlinker "-(" $(LIBS) -Xlinker "-)" -o $@
通過將所有需要被鏈接的靜態(tài)庫放入 -Xlinker "-(" 與 -Xlinker "-)" 之間,可以是 g++ 鏈接過程中,
自動(dòng)循環(huán)鏈接所有靜態(tài)庫,從而解決了原本的鏈接順序問題。
涉及鏈接選項(xiàng):-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.
回頁首 小結(jié)
本文介紹了 Linux 下三種標(biāo)準(zhǔn)庫鏈接的方式及各自利弊,同時(shí)還介紹了 Linux 下靜態(tài)庫的制作及使用方法,相信能夠給
大多數(shù)需要部署 Linux 應(yīng)用程序和編寫 Linux Makefile 的工程師提供有用的幫助。
|