一文彻底搞懂静态库和动态库,显示链接和隐式链接
定義:運(yùn)行時(shí)庫(kù) 靜態(tài)庫(kù) 動(dòng)態(tài)庫(kù)
運(yùn)行時(shí)庫(kù):Unix中一個(gè)典型的運(yùn)行時(shí)庫(kù)例子就是libc,它包含標(biāo)準(zhǔn)的C函數(shù),如,print(),exit()等等,用戶能創(chuàng)建他們自己的運(yùn)行庫(kù)(在Windows中是DLL),而具體的細(xì)節(jié)依賴(lài)編譯器和操作系統(tǒng)的。
靜態(tài)庫(kù):函數(shù)和數(shù)據(jù)被編譯進(jìn)一個(gè)二進(jìn)制文件(通常擴(kuò)展名為.lib),靜態(tài)庫(kù)實(shí)際上是在鏈接時(shí)被鏈接到EXE的,庫(kù)本身不需要與可執(zhí)行文件一起發(fā)行。
動(dòng)態(tài)庫(kù):用VC++創(chuàng)建的動(dòng)態(tài)庫(kù)包含兩個(gè)文件,一個(gè)lib文件和一個(gè)dll文件,這個(gè)lib文件就是引入庫(kù),不是靜態(tài)庫(kù),引入庫(kù)有時(shí)也叫輸入庫(kù)或?qū)霂?kù)。
注:windows操作系統(tǒng)下動(dòng)態(tài)庫(kù)和運(yùn)行時(shí)庫(kù)的擴(kuò)展名都是.dll,COM組件的擴(kuò)展名也是.dll,動(dòng)態(tài)庫(kù)的引入庫(kù)和靜態(tài)庫(kù)的擴(kuò)展名都是.lib。
靜態(tài)庫(kù)的特點(diǎn)和創(chuàng)建過(guò)程
靜態(tài)庫(kù)之所以稱(chēng)為【靜態(tài)庫(kù)】,是因?yàn)樵阪溄与A段,會(huì)將匯編生成的目標(biāo)文件.o與引用到的庫(kù)一起鏈接打包到可執(zhí)行文件中。因此對(duì)應(yīng)的鏈接方式稱(chēng)為靜態(tài)鏈接。
試想一下,靜態(tài)庫(kù)與匯編生成的目標(biāo)文件一起鏈接為可執(zhí)行文件,那么靜態(tài)庫(kù)必定跟.o文件格式相似。其實(shí)一個(gè)靜態(tài)庫(kù)可以簡(jiǎn)單看成是一組目標(biāo)文件(.o/.obj文件)的集合,即很多目標(biāo)文件經(jīng)過(guò)壓縮打包后形成的一個(gè)文件。靜態(tài)庫(kù)特點(diǎn)總結(jié)如下:
靜態(tài)庫(kù)對(duì)函數(shù)庫(kù)的鏈接是放在編譯時(shí)期完成的。
程序在運(yùn)行時(shí)與函數(shù)庫(kù)再無(wú)瓜葛,移植方便。
浪費(fèi)空間和資源,因?yàn)樗邢嚓P(guān)的目標(biāo)文件與牽涉到的函數(shù)庫(kù)被鏈接合成一個(gè)可執(zhí)行文件。
如下圖是靜態(tài)庫(kù)創(chuàng)建過(guò)程:
動(dòng)態(tài)庫(kù)的特點(diǎn)和創(chuàng)建過(guò)程
為什么還需要?jiǎng)討B(tài)庫(kù)?
為什么還需要?jiǎng)討B(tài)庫(kù),其實(shí)也就是靜態(tài)庫(kù)的特點(diǎn)導(dǎo)致。
空間浪費(fèi)是靜態(tài)庫(kù)的一個(gè)問(wèn)題。
另一個(gè)問(wèn)題是靜態(tài)庫(kù)對(duì)程序的更新、部署和發(fā)布頁(yè)會(huì)帶來(lái)麻煩。如果靜態(tài)庫(kù)libxx.lib更新了,所有使用它的應(yīng)用程序都需要重新編譯、發(fā)布給用戶(對(duì)于玩家來(lái)說(shuō),只是一個(gè)很小的改動(dòng),卻導(dǎo)致整個(gè)程序重新下載,全量更新)。
動(dòng)態(tài)庫(kù)在程序編譯時(shí)并不會(huì)被連接到目標(biāo)代碼中,而是在程序運(yùn)行是才被載入。不同的應(yīng)用程序如果調(diào)用相同的庫(kù),那么在內(nèi)存里只需要有一份該共享庫(kù)的實(shí)例,規(guī)避了空間浪費(fèi)問(wèn)題。動(dòng)態(tài)庫(kù)在程序運(yùn)行時(shí)才被載入,也解決了靜態(tài)庫(kù)對(duì)程序的更新、部署和發(fā)布頁(yè)會(huì)帶來(lái)麻煩。用戶只需要更新動(dòng)態(tài)庫(kù)即可,增量更新。
動(dòng)態(tài)庫(kù)特點(diǎn)總結(jié):
動(dòng)態(tài)庫(kù)把對(duì)一些庫(kù)函數(shù)的鏈接載入推遲到程序運(yùn)行的時(shí)期?!?/p>
可以實(shí)現(xiàn)進(jìn)程之間的資源共享。(因此動(dòng)態(tài)庫(kù)也稱(chēng)為共享庫(kù))
將一些程序升級(jí)變得簡(jiǎn)單。
甚至可以真正做到鏈接載入完全由程序員在程序代碼中控制(顯示調(diào)用)?!?/p>
?
Windows與Linux執(zhí)行文件格式不同,在創(chuàng)建動(dòng)態(tài)庫(kù)的時(shí)候有一些差異。
在Windows系統(tǒng)下的執(zhí)行文件格式是PE格式,動(dòng)態(tài)庫(kù)需要一個(gè)DllMain函數(shù)做初始化的入口,通常在導(dǎo)出函數(shù)的聲明時(shí)需要有_declspec(dllexport)關(guān)鍵字。
Linux下gcc編譯的執(zhí)行文件默認(rèn)是ELF格式,不需要初始化入口,亦不需要函數(shù)做特別的聲明,編寫(xiě)比較方便。
windows下調(diào)用動(dòng)態(tài)庫(kù)的方法:
1 隱式加載:即在程序中包含lib文件和.h文件,隱式鏈接有時(shí)稱(chēng)為靜態(tài)加載或加載時(shí)動(dòng)態(tài)鏈接。例如:
#include "somedll.h"
#pragma comment( lib, "somedll.lib")
然后就可以直接調(diào)用此dll中的函數(shù),注意運(yùn)行時(shí)仍然需要somedll.dll。
2 顯示加載:使用loadlibrary,GetProcAddress,FreeLibrary,不需要.h文件和.lib文件,但是要知道函數(shù)的原型。顯式鏈接有時(shí)稱(chēng)為動(dòng)態(tài)加載或運(yùn)行時(shí)動(dòng)態(tài)鏈接。
3 區(qū)別:如果在進(jìn)程啟動(dòng)時(shí)未找到 DLL,操作系統(tǒng)將終止使用隱式鏈接的進(jìn)程。同樣是在此情況下,使用顯式鏈接的進(jìn)程則不會(huì)被終止,并可以嘗試從錯(cuò)誤中恢復(fù)。
有關(guān)Win32 DLL,Unix共享庫(kù)及普通庫(kù)的詳細(xì)庫(kù)結(jié)構(gòu)信息請(qǐng)參考《鏈接器與加載器》一書(shū)。
鏈接庫(kù)的鏈接方式
1 確定要使用的鏈接方法:
有兩種類(lèi)型的鏈接:隱式鏈接和顯式鏈接。
隱式鏈接
應(yīng)用程序的代碼調(diào)用導(dǎo)出 DLL 函數(shù)時(shí)發(fā)生隱式鏈接。當(dāng)調(diào)用可執(zhí)行文件的源代碼被編譯或被匯編時(shí),DLL 函數(shù)調(diào)用在對(duì)象代碼中生成一個(gè)外部函數(shù)引用。若要解析此外部引用,應(yīng)用程序必須與 DLL 的創(chuàng)建者所提供的導(dǎo)入庫(kù)(.LIB 文件)鏈接。
導(dǎo)入庫(kù)僅包含加載 DLL 的代碼和實(shí)現(xiàn) DLL 函數(shù)調(diào)用的代碼。在導(dǎo)入庫(kù)中找到外部函數(shù)后,會(huì)通知鏈接器此函數(shù)的代碼在DLL 中。要解析對(duì) DLL 的外部引用,鏈接器只需向可執(zhí)行文件中添加信息,通知系統(tǒng)在進(jìn)程啟動(dòng)時(shí)應(yīng)在何處查找 DLL 代碼。
系統(tǒng)啟動(dòng)包含動(dòng)態(tài)鏈接引用的程序時(shí),它使用程序的可執(zhí)行文件中的信息定位所需的 DLL。如果系統(tǒng)無(wú)法定位 DLL,它將終止進(jìn)程并顯示一個(gè)對(duì)話框來(lái)報(bào)告錯(cuò)誤。否則,系統(tǒng)將 DLL 模塊映射到進(jìn)程的地址空間中。
如果任何 DLL 具有(用于初始化代碼和終止代碼的)入口點(diǎn)函數(shù),操作系統(tǒng)將調(diào)用此函數(shù)。在傳遞到入口點(diǎn)函數(shù)的參數(shù)中,有一個(gè)指定用以指示 DLL 正在附帶到進(jìn)程的代碼。如果入口點(diǎn)函數(shù)沒(méi)有返回 TRUE,系統(tǒng)將終止進(jìn)程并報(bào)告錯(cuò)誤。最后,系統(tǒng)修改進(jìn)程的可執(zhí)行代碼以提供 DLL 函數(shù)的起始地址。
與程序代碼的其余部分一樣,DLL 代碼在進(jìn)程啟動(dòng)時(shí)映射到進(jìn)程的地址空間中,且僅當(dāng)需要時(shí)才加載到內(nèi)存中。因此,由 .def 文件用來(lái)在 Windows 的早期版本中控制加載的 PRELOAD 和 LOADONCALL 代碼屬性不再具有任何意義。
顯式鏈接
大部分應(yīng)用程序使用隱式鏈接,因?yàn)檫@是最易于使用的鏈接方法。但是有時(shí)也需要顯式鏈接。下面是一些使用顯式鏈接的常見(jiàn)原因:
直到運(yùn)行時(shí),應(yīng)用程序才知道需要加載的 DLL 的名稱(chēng)。例如,應(yīng)用程序可能需要從配置文件獲取 DLL 的名稱(chēng)和導(dǎo)出函數(shù)名。
如果在進(jìn)程啟動(dòng)時(shí)未找到 DLL,操作系統(tǒng)將終止使用隱式鏈接的進(jìn)程。同樣是在此情況下,使用顯式鏈接的進(jìn)程則不會(huì)被終止,并可以嘗試從錯(cuò)誤中恢復(fù)。例如,進(jìn)程可通知用戶所發(fā)生的錯(cuò)誤,并讓用戶指定 DLL 的其他路徑。如果使用隱式鏈接的進(jìn)程所鏈接到的 DLL 中有任何 DLL 具有失敗的 DllMain 函數(shù),該進(jìn)程也會(huì)被終止。同樣是在此情況下,使用顯式鏈接的進(jìn)程則不會(huì)被終止。
因?yàn)?strong>Windows 在應(yīng)用程序加載時(shí)加載所有的 DLL,故隱式鏈接到許多 DLL 的應(yīng)用程序啟動(dòng)起來(lái)會(huì)比較慢。為提高啟動(dòng)性能,應(yīng)用程序可隱式鏈接到那些加載后立即需要的 DLL,并等到在需要時(shí)顯式鏈接到其他 DLL。
顯式鏈接下不需將應(yīng)用程序與導(dǎo)入庫(kù)鏈接。如果 DLL 中的更改導(dǎo)致導(dǎo)出序號(hào)更改,使用顯式鏈接的應(yīng)用程序不需重新鏈接(假設(shè)它們是用函數(shù)名而不是序號(hào)值調(diào)用 GetProcAddress),而使用隱式鏈接的應(yīng)用程序必須重新鏈接到新的導(dǎo)入庫(kù)。
下面是需要注意的顯式鏈接的兩個(gè)缺點(diǎn):
???????????? 1.?如果 DLL 具有 DllMain 入口點(diǎn)函數(shù),則操作系統(tǒng)在調(diào)用 LoadLibrary?的線程上下文中調(diào)用此函數(shù)。如果由于以前調(diào)用了LoadLibrary 但沒(méi)有相應(yīng)地調(diào)用 FreeLibrary 函數(shù)而導(dǎo)致 DLL 已經(jīng)附加到進(jìn)程,則不會(huì)調(diào)用此入口點(diǎn)函數(shù)。如果 DLL 使用 DllMain 函數(shù)為進(jìn)程的每個(gè)線程執(zhí)行初始化,顯式鏈接會(huì)造成問(wèn)題,因?yàn)檎{(diào)用 LoadLibrary(或AfxLoadLibrary)時(shí)存在的線程將不會(huì)初始化。服務(wù)器負(fù)載高,性能下降,導(dǎo)致無(wú)法及時(shí)的處理客戶端的請(qǐng)求,可能是服務(wù)器硬件本身需要升級(jí),另外一方面是程序自身的bug導(dǎo)致的吞吐量不夠,性能低、還有就是可能是架構(gòu)問(wèn)題,比如沒(méi)有分布式處理,無(wú)法動(dòng)態(tài)擴(kuò)容,基本上你需要查看內(nèi)存,CPU,磁盤(pán)使用情況,使用top,free ,df等命令來(lái)動(dòng)態(tài)查看找到異常指標(biāo)的進(jìn)程。
???????????? 2.?如果DLL 將靜態(tài)作用域數(shù)據(jù)聲明為 __declspec(thread),則在顯式鏈接時(shí) DLL會(huì)導(dǎo)致保護(hù)錯(cuò)誤。用 LoadLibrary 加載 DLL 后,每當(dāng)代碼引用此數(shù)據(jù)時(shí) DLL 就會(huì)導(dǎo)致保護(hù)錯(cuò)誤。(靜態(tài)作用域數(shù)據(jù)既包括全局靜態(tài)項(xiàng),也包括局部靜態(tài)項(xiàng)。)因此,創(chuàng)建DLL 時(shí)應(yīng)避免使用線程本地存儲(chǔ)區(qū),或者應(yīng)(在用戶嘗試動(dòng)態(tài)加載時(shí))告訴 DLL 用戶潛在的缺陷。
總結(jié)
以上是生活随笔為你收集整理的一文彻底搞懂静态库和动态库,显示链接和隐式链接的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: leetcode326. 3的幂 如此6
- 下一篇: 后缀树/后缀数组