1. 程式人生 > >C/C++靜態庫連結原理

C/C++靜態庫連結原理

前面我們學習了編譯連結的一些知識,現在來看看靜態庫連結的一些知識~

靜態庫本質上就是使用ar命令打包一堆.o檔案:

$ ar -r test.a myObj1.o myObj2.o
  •  

靜態庫沒有標準,不同的linux下都會有些細微的差別。大致的格式:

Global header
-----------------        +-------------------------------
File header 1       ---> | File name
File content 1  |        | File modification timestamp 
-----------------        | Owner ID
File header 2            | Group ID
File content 2           | File mode
-----------------        | File size in bytes
...                      | File magic
                         +-------------------------------

連結過程

連結器在連結靜態庫時,同連結一般的.o基本相似。連結過程大致如下:

這裡寫圖片描述

所有傳入連結器的.o都會被連結進最終的可執行程式;連結.o時,會將.o中的global symbol和unresolved symbol放入一個臨時表。

如果多個.o定義了相同的global symbol,那麼就會得到多重定義的連結錯誤。

如果連結結束了,unresolved symbol表不為空,那麼就會得到符號未定義的連結錯誤。

.a靜態庫處理本質上就是處理其中的每一個.o,不同的是,如果某個.o中沒有一個符號屬於unresolved symbol表,連結器此時懷疑該.o沒有必要,它就會被忽略

一些例子

// test.cpp
#include <stdio.h>

class Test {
public:
    Test() {
        printf("Test ctor\n");
    }
};

static Test s_test;
// lib.cpp
#include <stdio.h>

class Lib {
public:
    Lib() {
        printf("Lib ctor\n");
    }
};

static Lib s_lib;
// main.cpp
#include <stdio.h>

int main() {
    printf("main\n");
    return 0;
}
  •  

以上程式碼main.cpp中未引用任何test.cpp和lib.cpp中的符號。

結果如下:

$ g++ -o test test.o lib.o main.o
$ ./test
Lib ctor
Test ctor
main

如果某個.o被連結程序序,那麼其檔案內的靜態變數就會先於main初始化,上面程式碼中test.o和lib.o都被連結進來了。

但是如果把lib.o以靜態庫的形式進行連結,情況就不一樣了:為了做對比,基於以上的程式碼再加一個檔案,及修改main.cpp:

// libfn.cpp
int sum(int a, int b) {
    return a + b;
}
// main.cpp
#include <stdio.h>

int main() {
    printf("main\n");
    extern int sum(int, int);
    printf("sum: %d\n", sum(2, 3));
    return 0;
}

將libfn.o和lib.o打包為靜態庫:

$ ar -r libfn.a libfn.o lib.o
$ g++ -o test main.o test.o -lfn -L.
$ ./test
Test ctor
main
sum: 5

main.cpp中未引用任何lib.cpp中的符號,所以lib.o沒有被連結,導致其檔案內的靜態變數也未得到初始化。

調整連結順序:

# 將libfn.a的連結放在main.o前面

$ g++ -o test test.o -lfn main.o  -L.
main.o: In function `main':
main.cpp:(.text+0x19): undefined reference to `sum(int, int)'
collect2: ld returned 1 exit status

問題在於連結器在連結libfn.a的時候,發現libfn.o中的符號沒有被之前連結的*.o引用到,也就是沒有任何符號在unresolved symbol table中,所以libfn.o也被忽略。

一些引數

-whole-archive

-whole-archive選項告訴連結器把靜態庫中的所有.o都進行連結,針對以上例子:

$ g++ -o test -L. test.o -Wl,--whole-archive -lfn main.o -Wl,--no-whole-archive
$ ./test
Lib ctor
Test ctor
main
sum: 5

連lib.o也被連結了進來。-Wl選項告訴gcc將其作為連結器引數傳入;之所以在命令列結尾加上–no-whole-archive是為了告訴編譯器不要連結gcc預設的庫。

–start-group

--start-group archives --end-group
  •  

位於–start-group –end-group中的所有靜態庫將被反覆搜尋,而不是預設的只搜尋一次,直到不再有新的unresolved symbol產生為止。也就是說,出現在這裡的.o如果發現有unresolved symbol,則可能回到之前的靜態庫中繼續搜尋。

$ g++ -o test -L. test.o -Wl,--start-group -lfn main.o -Wl,--end-group
$ ./test
Test ctor
main
sum: 5