博客專欄

EEPW首頁 > 博客 > 靜態(tài)鏈接全過程

靜態(tài)鏈接全過程

發(fā)布人:電子禪石 時間:2021-04-13 來源:工程師 發(fā)布文章
寫在前面

本文大部分內容翻譯自Library order in static linking

靜態(tài)鏈接順序很重要

在gcc編譯時,靜態(tài)鏈接庫鏈接順序會影響鏈接結果和行為。
舉例如下:

$ 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會將所有的目標文件加入到鏈接過程中。

$ 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

當鏈接器遇到libsimplefunc.a時,它仍然沒有看到simplemain.o,這意味著func尚未出現在未定義的列表中。當鏈接器查看庫時,它會看到導出func的simplefunc.o。但由于它不需要func,因此該目標文件不包含在鏈接中。當鏈接器確實到達simplemain.o并且看到func確實是必需的時,它被添加到未定義的列表中(因為它不在導出的列表中)。鏈接器然后到達鏈接的末尾并且func仍未定義。

請注意在先前的鏈接順序中不會發(fā)生這種情況 - 因為simplemain.o首先出現,func在鏈接器看到庫之前位于未定義的列表中,因此導出它的目標文件確實包含在內。

從上面的例子中可以得出:靜態(tài)鏈接時,鏈接庫順序會影響鏈接結果。如果對象或庫AA需要來自庫BB的符號,則AA應該在鏈接器的命令行調用中位于庫BB 之前。
下面一節(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 當鏈接時,鏈接器維護兩個列表:
1.1 到目前為止遇到的所有目標文件(object)和庫(library)導出的符號列表,記做exports(導出的符號)
1.2 遇到的目標文件(object)和庫(library)請求導入但尚未找到的未定義符號列表,即undefined reference,記做imports(待導入的符號)

2 當鏈接器遇到新的目標文件(object)時,
2.1 它導出的符號會被添加到上面提到的導出符號列表中。 如果任何符號位于未定義列表中,則會從那里刪除它,因為現在已找到它。 如果導出列表中已有任何符號,則會出現“多重定義”錯誤:兩個不同的對象導出相同的符號,鏈接器會報錯
2.2 它導入的符號被添加到未定義符號列表中,除非它們可以在導出符號列表exports中找到。

3 當鏈接器遇到新時,事情會更有趣。 鏈接器遍歷庫中的所有目標文件(object),庫是object打包成的一個整體。 對于每一個,它首先查看它導出的符號
3.1 如果它導出的任何符號位于未定義列表中,則該對象將添加到鏈接中,并執(zhí)行下一步。 否則,將跳過下一步。
3.2 如果目標文件(object)已添加到鏈接中,則按上述方式對其進行處理 - 未定義和導出的符號將添加到符號表中。
3.3 如果庫中的任何目標文件(object)已包含在鏈接中,則會再次重新掃描整個庫,因為這個目標文件可能依賴庫中的其他目標文件。

鏈接器完成后,它會查看符號表。 如果任何符號保留在未定義的列表中,鏈接器將拋出“未定義的引用”錯誤。
在鏈接器查看庫之后,它將不會再次查看它,即鏈接器只查看庫一次。 即使它導出一些后來的庫可能需要的符號。 鏈接器返回重新掃描對象的唯一時間它已經在單個庫中發(fā)生 - 如上所述,一旦某個庫中的對象被帶入鏈接,同一庫中的所有其他對象將被重新掃描。 傳遞給鏈接器的標志可以調整這個過程 - 再次,我們稍后會看到一些例子。
另外,檢查庫時,如果不提供符號表所需的符號,則可以將其中的目標文件排除在鏈接之外。 這是靜態(tài)鏈接的一個非常重要的特性。 我之前提到過的C庫大量使用了這個功能,主要是將自身分解為每個函數的一個對象。 因此,例如,如果您的代碼使用的唯一C標準庫函數是strlen,則只有strlen.o將從libc.a進入鏈接 - 并且您的可執(zhí)行文件將非常小。

建議
  1. 在項目開發(fā)過層中盡量讓lib是垂直關系,避免循環(huán)依賴,越是底層的庫,越是往后面寫

  2. 若存在循環(huán)依賴的情況,可用以下方法解決

    Xlinker "-("-la -lb  -lc"-)" OR--start-group -la -lb lc -Wl,--end-group
    • 1

    • 2

    • 3

參考文檔

https://blog.csdn.net/caikunbob/article/details/85550000

*博客內容為網友個人發(fā)布,僅代表博主個人觀點,如有侵權請聯系工作人員刪除。



關鍵詞:

相關推薦

技術專區(qū)

關閉