【連接 1】與靜態連接庫連接

本文內容基於《CSAPP》第7章,只是符號解析的一部分,從使用的角度闡述了靜態庫的由來和使用,僅僅是我的看法,可能從編譯的角度看有不嚴謹的地方,如發現錯誤,還請指正,謝謝!程序員

1 靜態庫

首先咱們要知道,連接器將一組可重定位目標文件連接起來能夠組成一個可執行文件,如shell

$ ld -o prog ./a.o   ./b.o

但對於一些基礎的操做,如C標準庫中提供的printf、scanf、rand等一些列經常使用的函數,若是每次編譯,咱們都要操做帶有這些函數的可重定位目標文件,那麼一次簡單的編譯過程就會變成下面這樣:函數

$ gcc -o a.out main.c /usr/lib/printf.o   /usr/lib/scanf.o /usr/lib/rand.o ...

這樣一來,不只每次都要編寫冗長的命令行,並且程序員還必須維護一個包含所需的源文件或目標文件的文件夾。工具

但實際上,咱們在編譯咱們的程序時,並無考慮過這樣的問題,對於一個僅僅使用了標準庫中函數的源文件而言,也並不須要程序員手動的進行額外的連接操做。如對於下面main.c這個源文件而言,命令行

// main.c
#include<stdio.h>

int main()
{
    printf("Hello World!");
    return 0;
}

咱們只須要簡單的執行code

$ gcc -o a.out main.c

這是由於,標準庫中的函數都被編譯成了獨立的目標模塊,而後相關模塊會被封裝成一個單獨的靜態庫文件,如libc.a包含了C標準庫中的標準I/O、字符串操做等函數,libm.a包含了C標準庫中的整數數學函數,在執行連接操做時,編譯器的驅動程序會將這些標準靜態庫傳送給連接器,連接器會從中選擇適當的模塊同咱們本身編寫的目標模塊(main.o)連接起來獲得可執行文件。字符串

在Linux系統中,靜態庫以一種稱爲存檔(archive)的文件格式存儲,後綴名.a,它由一個頭和一系列的目標模塊構成,頭負責描述每一個成員目標模塊的位置和大小。編譯器

2 使用靜態庫

既然有標準庫,那咱們也能夠把本身編寫的函數、全局變量、宏等封裝成靜態庫。數學

例如咱們實現兩個自定義的整型操做函數,分別定義在下面兩個源文件中,io

// add.c
int add(int a, int b){
    return a+b
}
// sub.c
void sub(int a, int b){
    return a-b;
}

建立靜態庫須要使用AR工具,使用如下命令:

$ gcc -c add.c  sub.c
$ ar rcs libcal.a  add.o sub.o

如此便獲得了一個靜態庫libcal.a,在源文件中引用,便可使用靜態庫中定義的符號(非static函數、全局變量等)。

// main2.c
#include "cal.h"

int main()
{
    int a = 0, b = 3, c = 0;
    c = add(a, b);
    printf("%d", c);
    return 0;
}

編譯該源文件,

$ gcc -c main2.c
$ gcc -static -o prog2c main2.o

或者等價地使用,

$ gcc -c main2.c
$ gcc -static -o prog2c main2.o -L. -lcal

連接器運行時,它就會斷定main2.o引用了add.o定義的add符號,因此複製add.o到可執行文件,此外,他也會從/usr/lib/libc.a中複製printf所在的目標文件到可執行文件。

3 連接器如何使用靜態庫來解析引用

命令行上庫和目標文件的順序很是重要,若是咱們對上一條命令作一些小小的改動,使之變爲

$ gcc -static -o prog2c ./libcal.a main2.o

這條命令的執行就會報錯「undefined reference to 'add'」,之因此出現這樣的狀況,是連接器解析外部引用的方式致使的。

連接器是按照命令行上從左到右的順序來掃描文件的,在掃描文件時,連接器會維護三個集合:E(這個集合中的文件會被合併起來造成可執行文件)、U(未解析的符號)以及D(在前面輸入文件中已定義的符號集合),三個集合初始爲空。

  • 對於命令行上的每一個文件f,連接器會首先判斷這一文件是目標文件仍是靜態庫文件。若該文件是一個目標文件,則放入E中,並修改U和D來反映f中的符號定義和引用。
  • 但若是f是一個靜態庫文件,那麼連接器就試圖對U中未解析的符號和f的成員所定義的符號進行匹配。若是f中的某一成員m定義了一個符號來解析U中的一個引用,那麼就將m加入E中,再相應地修改U和D中的內容來反映m中的符號定義和引用,對f中的全部成員逐個進行匹配操做直至U和D再也不發生變化,鏈接器便開始處理下一個文件。
  • 當連接器掃描完全部命令行中的文件後,若U是空的,那麼鏈接及就會合並和重定位E中的文件,獲得一個可執行文件;不然,連接器就會報錯並終止。

如今,是否是理解了上面的錯誤了呢,連接器掃描到libcal.a時,U中尚是空的,故直接繼續掃描後面的main2.o,而後,main2.o中的add符號未解析,被加入到U中,隨後,結束掃描,U中非空,連接器報錯。

須要注意的是,庫和庫之間也可能存在依賴關係,故使用多個庫時要注意其前後順序,若存在相互依賴的關係,則能夠選擇在命令行上重複庫,以下面一條命令中,libx.a調用了liby.a中的函數,liby.a又調用了libx.a中的函數,

$ gcc foo.c libx.a liby.a libx.a

固然,把二者合併爲單獨的一個靜態庫也不失爲一種好方法。

相關文章
相關標籤/搜索