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