1. 程式人生 > >徹底理解連結器

徹底理解連結器

在介紹本章的主題之前,我們先來看幾個問題:

問題一

寫C/C++的同學應該經常遇到這樣的一個Error:

    `undefined reference to ABC`

在遇到這樣的問題時你知道這背後到底哪裡出問題了嗎? 你通常都能順利解決類似問題嗎?

問題二

作為世界上最大的同性交友網站GitHub,裡面有很多很棒的專案,一般我們或者直接下載其釋出版(release version),或者下載原始碼自己編譯,不管是直接下載釋出版還是自己編譯,最終都會得到一個(或幾個)以.so或者.a為結尾的檔案(Windows下為DLL檔案或者lib檔案),這時你知道該怎麼把這些.so或者.a檔案引入你自己的專案嗎?當然如果你去搜索一下也能得到答案,但是你知道這些答案背後的原理嗎?

問題三 你的同學、同事在工作學習中可能不時就會提及到靜態連結庫動態連結庫靜態連結動態連結,每次聽到這些詞彙的時候在你腦海裡,A)對此有很清晰的認知;B)一頭霧水不知道他們在說些什麼,你屬於A還是B?

如果你還不能很好的解決上面前兩個問題且對於問題三屬於B,那麼接下來你就要好好看這篇文章啦,解決這幾個問題的關鍵就是這篇文章要介紹的連結器(Linker),雖然現代的整合開發環境IDE比如Visual Studio已經對程式設計師遮蔽了大部分連結器的工作,但理解連結器將極大提高你對工程的駕馭能力,也許你現在還不是很清楚,讀完這篇文章你就能明白啦。

#什麼是連結器(Linker)#

   讓我們引用維基百科中對連結器的定義:    

> "a linker or link editor is a computer utility program that takes one or more object files generated by a compiler and combines them into a single executable file, library file, or another 'object' file."

   如果你看不太懂沒有關係,我來翻譯一下,連結器是一個將編譯器產生的目標檔案打包成可執行檔案或者庫檔案或者目標檔案的程式。這個翻譯比較拗口,不太好理解,這句話的意思具體如下:    首先是連結器的本質,連結器本質上也是一個程式,本質上和我們經常使用的普通程式沒什麼不同。    其次是連結器的輸入,我們經常使用的程式比如播放器,其輸入是一個MP4檔案,而連結器的輸入是編譯器編譯好的目標檔案(object file,如果你不理解什麼是目標檔案,請參考之前的文章《不簡單的hello world之C標準庫》)。    最後是連結器的輸出,連結器在將目標檔案打包處理後,生成或者可執行檔案,或者庫,或者目標檔案。    從這個定義中能夠看出,連結器的作用有點類似於我們經常使用的壓縮軟WinRAR(Linux下是tar),壓縮軟體將一堆檔案打包壓縮成一個壓縮檔案,而連結器和壓縮軟體的區別在於連結器是將多個目標檔案打包成一個檔案而不進行壓縮。那麼連結器到底是如何工作的呢,我們接著往下看。     #連結器可操作的元素#

連結器可操作的最小元素是一個簡單的目標檔案,通常我們寫的.c原始檔編譯後就生成了對應的目標檔案,我們寫的實現檔案比如list.c編譯後就生成了對應的目標檔案list.o(Windows下為list.obj),這個list.o就是連結器可以操作的最小元素。我們見到的所有應用程式,小到自己實現的hello world程式,大到複雜的比如瀏覽器,網路伺服器等,都是連結器將一個個所需要用到的目標檔案彙集起來最終形成了非常複雜的應用程式(Windows下是我們常見的EXE檔案,Linux下為ELF檔案)。

我們可以把最終的應用程式想象成一座房子,構建房子的最基本的原材料就是磚,房子中各個模組像牆面,地面,屋頂等都是由一塊塊磚構築成的。而這裡的目標檔案就好比構建房子時最基本的磚。房子的各個模組就好比我們是用的靜態庫,動態庫。無論多麼複雜龐大的應用程式,對於連結器來說最基本的構建材料都是目標檔案。連結器可以將目標檔案連結器成為各種庫以方便使用,然後連結器將目標檔案以及程式依賴的各種庫再次連結從而形成最終的可執行檔案。

接下來我們具體看一下連結器是如何工作的。

#連結器是如何工作的#

在連結器可操作的元素一節中我們提到,所有的應用程式都是連結器將所需要的一個個簡單的目標檔案彙集起來形成的。你可以將這個過程想象成拼圖遊戲,每個拼塊就是一個簡單的目標檔案: 1,拼圖遊戲當中的每個拼塊都依賴於其它拼塊提供的拼介面,這就好比我們寫的程式模組依賴於其它模組提供的程式設計介面,比如我們在list.c中實現了一種特定的連結串列資料結構,其它模組需要使用這種連結串列,這就是模組間的依賴。而連結器其中一項任務就是要確保提供給連結器進行連結的目標檔案集合之間依賴是成立的(也就是說,不會出現在被依賴的模組中連結器找不到需要的介面),這就是後面我們要講到的符號決議(Symbol Resolution),開篇提到的第一個問題就來自這個過程。