寫在前面本文大部分內(nèi)容翻譯自Library order in static linking
靜態(tài)鏈接順序很重要在gcc編譯時,靜態(tài)鏈接庫鏈接順序會影響鏈接結(jié)果和行為。
舉例如下:
$ cat simplefunc.c
int func(int i) {
return i + 21;
}
$ cat simplemain.c
int func(int);
int main(int argc, const char* argv[]) {
return func(argc);
}
$ gcc -c simplefunc.c
$ gcc -c simplemain.c
$ gcc simplefunc.o simplemain.o
$ ./a.out ; echo $?
22
$ gcc simplemain.o simplefunc.o
$ ./a.out ; echo $?
22123456789101112131415161718192021
對于object文件,鏈接順序是沒有影響的,gcc會將所有的目標(biāo)文件加入到鏈接過程中。
$ ar r libsimplefunc.a simplefunc.o
$ ranlib libsimplefunc.a
$ gcc simplemain.o -L. -lsimplefunc
$ ./a.out ; echo $?
22
$ gcc -L. -lsimplefunc simplemain.o
simplemain.o: In function 'main':
simplemain.c:(.text+0x15): undefined reference to 'func'
collect2: ld returned 1 exit status12345678910
當(dāng)鏈接器遇到libsimplefunc.a時,它仍然沒有看到simplemain.o,這意味著func尚未出現(xiàn)在未定義的列表中。當(dāng)鏈接器查看庫時,它會看到導(dǎo)出func的simplefunc.o。但由于它不需要func,因此該目標(biāo)文件不包含在鏈接中。當(dāng)鏈接器確實到達(dá)simplemain.o并且看到func確實是必需的時,它被添加到未定義的列表中(因為它不在導(dǎo)出的列表中)。鏈接器然后到達(dá)鏈接的末尾并且func仍未定義。
請注意在先前的鏈接順序中不會發(fā)生這種情況 - 因為simplemain.o首先出現(xiàn),func在鏈接器看到庫之前位于未定義的列表中,因此導(dǎo)出它的目標(biāo)文件確實包含在內(nèi)。
從上面的例子中可以得出:靜態(tài)鏈接時,鏈接庫順序會影響鏈接結(jié)果。如果對象或庫AA需要來自庫BB的符號,則AA應(yīng)該在鏈接器的命令行調(diào)用中位于庫BB 之前。
下面一節(jié),我們將從原理上來了解鏈接庫的順序是如何影響鏈接結(jié)果的!
靜態(tài)鏈接過程程序靜態(tài)鏈接的過程如下,舉例說明(翻譯Library order in static linking的The linking process一節(jié))
$ gcc main.o -L/some/lib/dir -lfoo -lbar -lbaz1
注:庫指library,對象指object,此處要注意區(qū)分。
1 當(dāng)鏈接時,鏈接器維護(hù)兩個列表:
1.1 到目前為止遇到的所有目標(biāo)文件(object)和庫(library)導(dǎo)出的符號列表,記做exports(導(dǎo)出的符號)
1.2 遇到的目標(biāo)文件(object)和庫(library)請求導(dǎo)入但尚未找到的未定義符號列表,即undefined reference,記做imports(待導(dǎo)入的符號)
2 當(dāng)鏈接器遇到新的目標(biāo)文件(object)時,
2.1 它導(dǎo)出的符號會被添加到上面提到的導(dǎo)出符號列表中。 如果任何符號位于未定義列表中,則會從那里刪除它,因為現(xiàn)在已找到它。 如果導(dǎo)出列表中已有任何符號,則會出現(xiàn)“多重定義”錯誤:兩個不同的對象導(dǎo)出相同的符號,鏈接器會報錯
2.2 它導(dǎo)入的符號被添加到未定義符號列表中,除非它們可以在導(dǎo)出符號列表exports中找到。
3 當(dāng)鏈接器遇到新庫時,事情會更有趣。 鏈接器遍歷庫中的所有目標(biāo)文件(object),庫是object打包成的一個整體。 對于每一個,它首先查看它導(dǎo)出的符號
3.1 如果它導(dǎo)出的任何符號位于未定義列表中,則該對象將添加到鏈接中,并執(zhí)行下一步。 否則,將跳過下一步。
3.2 如果目標(biāo)文件(object)已添加到鏈接中,則按上述方式對其進(jìn)行處理 - 未定義和導(dǎo)出的符號將添加到符號表中。
3.3 如果庫中的任何目標(biāo)文件(object)已包含在鏈接中,則會再次重新掃描整個庫,因為這個目標(biāo)文件可能依賴庫中的其他目標(biāo)文件。
鏈接器完成后,它會查看符號表。 如果任何符號保留在未定義的列表中,鏈接器將拋出“未定義的引用”錯誤。
在鏈接器查看庫之后,它將不會再次查看它,即鏈接器只查看庫一次。 即使它導(dǎo)出一些后來的庫可能需要的符號。 鏈接器返回重新掃描對象的唯一時間它已經(jīng)在單個庫中發(fā)生 - 如上所述,一旦某個庫中的對象被帶入鏈接,同一庫中的所有其他對象將被重新掃描。 傳遞給鏈接器的標(biāo)志可以調(diào)整這個過程 - 再次,我們稍后會看到一些例子。
另外,檢查庫時,如果不提供符號表所需的符號,則可以將其中的目標(biāo)文件排除在鏈接之外。 這是靜態(tài)鏈接的一個非常重要的特性。 我之前提到過的C庫大量使用了這個功能,主要是將自身分解為每個函數(shù)的一個對象。 因此,例如,如果您的代碼使用的唯一C標(biāo)準(zhǔn)庫函數(shù)是strlen,則只有strlen.o將從libc.a進(jìn)入鏈接 - 并且您的可執(zhí)行文件將非常小。
建議在項目開發(fā)過層中盡量讓lib是垂直關(guān)系,避免循環(huán)依賴,越是底層的庫,越是往后面寫
若存在循環(huán)依賴的情況,可用以下方法解決
Xlinker "-("-la -lb -lc"-)" OR--start-group -la -lb lc -Wl,--end-group
參考文檔https://blog.csdn.net/caikunbob/article/details/85550000