您好,登錄后才能下訂單哦!
講動態鏈接之前,得先說說符號重定位。
c/c++ 程序的編譯是以文件為單位進行的,因此每個 c/cpp 文件也叫作一個編譯單元(translation unit), 源文件先是被編譯成一個個目標文件, 再由鏈接器把這些目標文件組合成一個可執行文件或庫,鏈接的過程,其核心工作是解決模塊間各種符號(變量,函數)相互引用的問題,對符號的引用本質是對其 在內存中具體地址的引用,因此確定符號地址是編譯,鏈接,加載過程中一項不可缺少的工作,這就是所謂的符號重定位。本質上來說,符號重定位要解決的是當前編譯單元如何訪問「外部」符號這個問題。
因為編譯是以源文件為單位進行的,編譯器此時并沒有一個全局的視野,因此對一個編譯單元內的符號它是無力確定其最終地址的,而對于可執行文件來說, 在現代操作系統上,程序加載運行的地址是固定或可以預期的,因此在鏈接時,鏈接器可以直接計算分配該文件內各種段的絕對或相對地址。所以對于可執行文件來說,符號重定位是在鏈接時完成的(如果可執行文件引用了動態庫里的函數,則情況稍有不同)。但對于動態鏈接庫來說,因為動態庫的加載是在運行時,且加載的地址不固定,因此沒法事先確定該模塊的起始地址,所以對動態庫的符號重定位,只能推遲。
符號重定位既指在當前目標文件內進行重定位,也包括在不同目標文件,甚至不同模塊間進行重定位,這里面有什么不同嗎?如果是同一個目標文件內,或者 在同一個模塊內,鏈接后,各個符號的相對地址就已經確定了,看起來似乎不用非得要知道最后的絕對地址才能引用這些符號,這說起來好像也有道理,但事實不是 這樣,x86 上 mov 之類訪問程序中數據段的指令,它要求操作數是絕對地址,而對于函數調用,雖然是以相對地址進行調用,但計算相對地址也只限于在當前目標文件內進行,跨目標 文件跨模塊間的調用,編譯期也是做不到的,只能等鏈接時或加載時才能進行相對地址的計算,因此重定位這個過程是不能缺少的,事實上目前來說,對于動態鏈接 即使是當前目標文件內,如果是全局非靜態函數,那么它也是需要進行重定位的。
注:
動態鏈接,在可執行文件裝載時或運行時,由操作系統的裝載程序加載庫。大多數操作系統將解析外部引用(比如庫)作為加載過程的一部分。在這些系統上,可執行文件包含一個叫做import directory的表,該表的每一項包含一個庫的名字。根據表中記錄的名字,裝載程序在硬盤上搜索需要的庫,然后將其加載到內存中預先不確定的位置,之后根據加載庫后確定的庫的地址更新可執行程序。可執行程序根據更新后的庫信息調用庫中的函數或引用庫中的數據。這種類型的動態加載成為裝載時加載 ,被包括Windows和Linux的大多數系統采用。裝載程序在加載應用軟件時要完成的最復雜的工作之一就是加載時鏈接。
其他操作系統可能在運行時解析引用。在這些系統上,可執行程序調用操作系統API,將庫的名字,函數在庫中的編號和函數參數一同傳遞。操作系統負責立即解析然后代表應用調用合適的函數。這種動態鏈接叫做運行時鏈接 。因為每個調用都會有系統開銷,運行時鏈接要慢得多,對應用的性能有負面影響。現代操作系統已經很少使用運行時鏈接。
可以動態鏈接的庫,在Windows上是dynamic link library (DLL),在UNIX或Linux上是Shared Library。庫文件是預先編譯鏈接好的可執行文件,存儲在計算機的硬盤上。大多數情況下,同一時間多個應用可以使用一個庫的同一份拷貝,操作系統不需要加載這個庫的多個實例。
動態鏈接的最大缺點是可執行程序依賴分別存儲的庫文件才能正確執行。如果庫文件被刪除了,移動了,重命名了或者被替換為不兼容的版本了,那么可執行程序就可能工作不正常。這就是常說的DLL-hell。
g++編譯參數:
-shared 該選項指定生成動態連接庫(讓連接器生成T類型的導出符號表,有時候也生成弱連接W類型的導出符號),不用該標志外部程序無法連接。相當于一個可執行文件。
-fPIC:表示編譯為位置獨立的代碼,不用此選項的話編譯后的代碼是位置相關的所以動態載入時是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的。
-L.:表示要連接的庫在當前目錄中
-l Detour:編譯器查找動態連接庫時有隱含的命名規則,即在給出的名字前面加上lib,后面加上.so來確定庫的名稱
-I 項目中include包含的頭文件尋找路徑
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。