在連接器可操做的元素這一節中咱們提到,連接器能夠操做的最小單元爲目標文件,也就是說咱們見到的不管是靜態庫、動態庫、可執行文件,都是基於目標文件構建出來的。目標文件就比如樂高積木中最小的零部件。微信
給定目標文件以及連接選項,連接器能夠生成兩種庫,分別是靜態庫以及動態庫,如圖所示,給定一樣的目標文件,連接器能夠生成兩種不一樣類型的庫,接下來咱們分別介紹。函數
靜態庫工具
假設這樣一個應用場景,基礎設計團隊設計了好多實用而且功能強大的工具函數,業務團隊須要用到裏面的各類函數。每次新添加其中一個函數,業務團隊都要去找相應的實現文件並修改連接選項。使用靜態庫就能夠解決這個問題。靜態庫在Windows下是以.lib爲後綴的文件,Linux下是以.a爲後綴的文件。spa
爲解決上述問題,基礎設計團隊能夠提早將工具函數集合打包編譯連接成爲靜態庫提供給業務團隊使用,業務團隊在使用時只要連接該靜態庫就能夠了,每次新使用一個工具函數的時候,只要該函數在此靜態庫中就無需進行任何修改。操作系統
你能夠簡單的將靜態庫理解爲由一堆目標文件打包而成, 使用者只須要使用其中的函數而無需關注該函數來自哪一個目標文件(找到函數實現所在的目標文件是連接器來完成的,從這裏也能夠看出,不是全部靜態庫中的目標文件都會用到,而是用到哪一個連接器就連接哪一個)。靜態庫極大方便了對其它團隊所寫代碼的使用。.net
靜態鏈接設計
靜態庫是連接器經過靜態連接將其和其它目標文件合併生成可執行文件的,以下圖一所示,而靜態庫只不過是將多個目標文件進行了打包,在連接時只取靜態庫中所用到的目標文件,所以,你能夠將靜態連接想象成以下圖2所示的過程。blog
靜態庫是使用庫的最簡單的方法,若是你想使用別人的代碼,找到這些代碼的靜態庫並簡單的和你的程序連接就能夠了。靜態連接生成的可執行文件在運行時不依賴任何其它代碼,要理解這句話,咱們須要知道靜態連接下,可執行文件是如何生成的。內存
靜態連接下可執行文件的生成get
在上一節中咱們知道,能夠將靜態連接簡單的理解爲連接器將使用到的目標文件集合進行拼裝,拼裝以後就生成了可執行文件,同時咱們在目標文件裏有什麼這一節中知道,目標文件分紅了三段,代碼段,數據段,符號表,那麼在靜態連接下可執行文件的生成過程如圖所示:
從上圖中咱們能夠看到可執行文件的特色:
可執行文件和目標文件同樣,也是由代碼段和數據段組成。
每一個目標文件中的數據段都合併到了可執行文件的數據段,每一個目標文件當中的代碼段都合併到了可執行文件的代碼段。
目標文件當中的符號表並無合併到可執行文件當中,由於可執行文件不須要這些字段。
可執行文件和目標文件沒有什麼本質的不一樣,可執行文件區別於目標文件的地方在於,可執行文件有一個入口函數,這個函數也就是咱們在C語言當中定義的main函數,main函數在執行過程當中會用到全部可執行文件當中的代碼和數據。而這個main函數是被誰調用執行的呢,答案就是操做系統(Operating System),這也是後面文章當中要重點介紹的內容。
如今你應該對可執行文件有一個比較形象的認知了吧。你能夠把可執行文件生成的過程想象成裝訂一本書,一本書中一般有好多章節,這些章節是你本身寫的,且一本書不可避免的要引用其它著做。靜態連接這個過程就比如不但要裝訂你本身寫的文章,並且也把你引用的其它人的著做也直接裝訂進了你的書裏,這裏不考慮版權問題 :),這些工做完成後,只須要按一下訂書器,一本書就製做完成啦。
在這個比喻中,你寫的各個章節就比如你寫的代碼,引用的其它人的著做就比如使用其它人的靜態庫,裝訂成一本書就比如可執行文件的生成。
靜態連接是使用庫的最簡單最直觀的形式, 從靜態連接生成可執行文件的過程當中能夠看到,靜態連接會將用到的目標文件直接合併到可執行文件當中,想象一下,若是有這樣的一種靜態庫,幾乎全部的程序都要使用到,也就是說,生成的全部可執行文件當中都有一份如出一轍的代碼和數據,這將是對硬盤和內存的極大浪費,假設一個靜態庫爲2M,那麼500個可執行文件就有1G的數據是重複的。如何解決這個問題呢,答案就是使用動態庫。
動態庫
在前三小節中咱們瞭解了靜態庫、靜態連接以及使用靜態連接下可執行文件是如何生成的。接下里咱們講解一下動態庫,那麼什麼是動態庫?
動態庫(Dynamic Library),又叫共享庫(Shared Library),動態連接庫等,在Windows下就是咱們常見的大名鼎鼎的DLL文件了,Windows系統下大量使用了動態庫。在Linux下動態庫是以.so爲後綴的文件,同時以lib爲前綴,好比進行數字計算的動態庫Math,編譯連接後產生的動態庫就叫作libMath.so。從名字中咱們知道動態庫也是庫,本質上動態庫一樣包含咱們已經熟悉的代碼段、數據段、符號表。只不過動態庫的使用方式以及使用時間和靜態庫不太同樣。
在前面幾個小節中咱們知道,使用靜態庫時,靜態庫的代碼段和數據段都會直接打包copy到可執行文件當中,使用靜態庫無疑會增大可執行文件的大小,同時若是程序都須要某種類型的靜態庫,好比libc,使用靜態連接的話,每一個可執行文件當中都會有一份一樣的libc代碼和數據的拷貝,如圖所示,動態庫的出現解決了此類問題。
動態庫容許使用該庫的可執行文件僅僅包含對動態庫的引用而無需將該庫拷貝到可執行文件當中。也就是說,同靜態庫進行總體拷貝的方式不一樣,對於動態庫的使用僅僅須要可執行文件當中包含必要的信息便可,爲了方便理解,你能夠將可執行文件當中保存的必要信息僅僅理解爲須要記錄動態庫的名字就能夠了,如圖所示,同靜態庫相比,動態庫的使用減小了可執行文件的大小。
從上面這張圖中能夠看出,動態庫的使用解決了靜態連接當中可執行文件過大的問題。咱們在前幾節中將靜態連接生成可執行文件的過程比做了裝訂一本書,靜態連接將引用的其它人的著做也裝訂到了書裏,而動態連接能夠想象成做者僅僅在引用的地方寫了一句話,好比引用了《碼農的荒島求生》,那麼做者就在引用的地方寫上「此處參考《碼農的荒島求生》」,那麼讀者在讀到這裏的時候會本身去找到碼農的荒島求生這本書並查找相應的內容,其實這個過程就是動態連接的基本思想了。
到這裏咱們就能夠回答以前提到過的問題了,helloworld程序中的printf函數究竟是在哪裏定義的,答案就是該函數是在libc.so當中定義的,Linux下編譯連接生成可執行文件時會默認動態連接libc.so(Windows下也是一樣的道理),使用ldd命令就會發現每一個可執行文件都依賴libc.so。所以雖然你從沒有看到過printf的定義也能夠正確的使用這個函數。
接下來咱們講解一下動態連接。
《完全理解連接器:四,庫與可執行文件的生成》,歡迎關注微信公衆號,碼農的荒島求生,獲取更多內容。
本文分享自微信公衆號 - 碼農的荒島求生(escape-it)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。