很久沒(méi)有寫(xiě)程式設(shè)計(jì)入門(mén)知識(shí)的相關(guān)文章了,這篇文章要來(lái)談?wù)劤淌綆?kù) (Library) 連結(jié),以及關(guān)於 MSVC 與 CRT 之間的種種恩怨情仇。 如果你使用的作業(yè)系統(tǒng)是 Linux、Mac 或其他非 Windows 平臺(tái),你可以忽略這篇文章;如果你使用的作業(yè)系統(tǒng)是 Windows 平臺(tái),但沒(méi)有用 Microsoft Visual Studio C++(以下簡(jiǎn)稱為 MSVC)軟體撰寫(xiě) C++ 程式的話,這篇文章對(duì)你的幫助可能很有限;但如果你的作業(yè)系統(tǒng)是 Windows,而且你使用的程式整合開(kāi)發(fā)環(huán)境是 MSVC 軟體撰寫(xiě) C++ 程式的話,這篇文章應(yīng)該能夠幫助你釐清一些重要的基礎(chǔ)觀念。 身為程式設(shè)計(jì)者,在學(xué)習(xí)程式設(shè)計(jì)的過(guò)程中,你是否曾經(jīng)遇過(guò)某些看起來(lái)不知所云的錯(cuò)誤訊息,卻不知該如何解決?例如當(dāng)你快快樂(lè)樂(lè)地寫(xiě)完程式,並且確認(rèn)所有的程式碼都能成功通過(guò)編譯之後,接著執(zhí)行「建置方案」(Build Solution) 的步驟,結(jié)果卻跑出一堆莫名其妙的錯(cuò)誤:
以一般的情況來(lái)說(shuō),如果在你的程式專案中有使用某些由他人所撰寫(xiě)的第三方程式庫(kù)或是開(kāi)源專案的程式庫(kù),比較容易會(huì)發(fā)生上述的錯(cuò)誤狀況。從上述這些看似離奇而令人摸不著頭緒的錯(cuò)誤訊息中,我們大概可以猜測(cè)問(wèn)題點(diǎn)應(yīng)該在於 LIBCMTD.lib 與 MSVCRTD.lib 這兩個(gè)程式庫(kù)身上。但到底什麼是 LIBCMTD.lib 和 MSVCRTD.lib?在我們的程式碼中有使用這些程式庫(kù)嗎?
答案是肯定的。 熟悉 C 語(yǔ)言的程式設(shè)計(jì)者都知道,如果要使用 printf()、scanf() 或者 fopen() 等等 C 語(yǔ)言的基本 I/O 操作函式時(shí),首先必須用 #include 語(yǔ)法將 stdio.h 這個(gè)標(biāo)頭檔納入我們的程式碼中。藉由 stdio.h 中對(duì)這些 I/O 操作函式所做出的函式宣告 (function declaration),編譯器 (Compiler) 才得以確認(rèn) printf、scanf 以及 fopen 等等都是合法可用的函式。 而當(dāng)我們撰寫(xiě)的程式碼經(jīng)過(guò)編譯器產(chǎn)出 OBJ 形式的檔案之後,需要再經(jīng)由連結(jié)器 (Linker) 的處理程序,將程式碼中全部有使用到的函式定義 (function definition) 連結(jié)建置起來(lái),才能夠產(chǎn)生出最後的程式執(zhí)行檔。問(wèn)題來(lái)了,我們知道 printf、scanf 以及 fopen 的函式宣告存在於 stdio.h 當(dāng)中,但是這些傢伙的函式定義,也就是真正的實(shí)做程式碼,究竟存放在什麼地方呢? 在 C 語(yǔ)言的標(biāo)準(zhǔn)程式庫(kù)中。 由 C 語(yǔ)言所制訂的標(biāo)準(zhǔn)程式庫(kù),稱之為「執(zhí)行階段程式庫(kù)」,也就是 C Run-Time Library,通??珊?jiǎn)稱為 CRT。在 C 語(yǔ)言的標(biāo)準(zhǔn)程式庫(kù)中,包含了一組常用的基礎(chǔ)函式,例如 I/O 處理與字串操作程序等等,所以只要我們使用 C 語(yǔ)言撰寫(xiě)程式碼,就一定要將編譯完成後的程式碼 OBJ 檔,連結(jié)至 C 語(yǔ)言的執(zhí)行階段程式庫(kù),才能夠產(chǎn)生出合法的 C 語(yǔ)言程式執(zhí)行檔。 而 CRT 並非只有單一一種版本存在。事實(shí)上,除了可以依「除錯(cuò)」與「釋出」用途分成兩個(gè)版本之外,兩者又可分別衍生分出「靜態(tài)連結(jié)」與「動(dòng)態(tài)連結(jié)」兩種形式:
雖然這四個(gè) CRT 版本的用途與使用方式各不相同,但卻有個(gè)共通的特點(diǎn),就是它們都是滿足執(zhí)行緒安全需求,可在多執(zhí)行緒程式碼中安全使用的程式庫(kù)版本。事實(shí)上,在過(guò)去 MSVC 6 的版本中,本來(lái)還有另外兩個(gè) LIBCD.lib(除錯(cuò)版本)與 LIBC.lib 程式庫(kù),是專門(mén)給單執(zhí)行緒程式使用的 CRT 版本,但是這兩個(gè)選項(xiàng)自 MSVC 2005 開(kāi)始就從設(shè)定選項(xiàng)中被刪除掉了,所以現(xiàn)在大多數(shù)程式設(shè)計(jì)者使用的都是多執(zhí)行緒的 CRT 版本。 在程式庫(kù)連結(jié) (library linking) 的行為中,靜態(tài)連結(jié)和動(dòng)態(tài)連結(jié)的分別,在於使用靜態(tài)連結(jié)時(shí),會(huì)直接將程式庫(kù)的函式定義嵌入執(zhí)行檔之中,而使用動(dòng)態(tài)連結(jié)時(shí),程式庫(kù)的函式定義則存在於另外的獨(dú)立檔案,通常是 DLL 格式的檔案中,然後與程式執(zhí)行檔一同發(fā)佈給使用者。因此在檔案的尺寸上,使用動(dòng)態(tài)連結(jié)的執(zhí)行檔檔案,通常會(huì)比使用靜態(tài)連結(jié)的執(zhí)行檔檔案來(lái)得更小一些。 使用動(dòng)態(tài)連結(jié) CRT 版本的好處,是能夠?qū)⒔?jīng)常使用到的標(biāo)準(zhǔn)程式庫(kù)們獨(dú)立出來(lái),放在 Windows 的系統(tǒng)資料夾中,以減少我們建置出來(lái)的執(zhí)行檔檔案尺寸。但反過(guò)來(lái)說(shuō),使用動(dòng)態(tài)連結(jié) CRT 版本的缺點(diǎn)也在於這些與執(zhí)行檔相依為命的 DLL 檔案上。舉例來(lái)說(shuō),如果程式以 MSVC 2005 建置出 Debug 組態(tài)的執(zhí)行檔,則此執(zhí)行檔需要有 msvcr80d.dll 存在才能順利執(zhí)行;如果是 Release 組態(tài),則相依於 msvcr80.dll。但是如果你把相同的程式碼拿到 MSVC 2008 上建置,產(chǎn)生出來(lái)的執(zhí)行檔則相依於 msvcr90d.dll 與 msvcr90.dll 兩個(gè)不同的 DLL 檔案。不同版本的 MSVC,都會(huì)有各自不同的相依 DLL 檔案。 在 MSVC 的程式專案中,如何指定程式碼要使用靜態(tài)連結(jié)或者動(dòng)態(tài)連結(jié)的 CRT 版本?其實(shí)很容易,只要在專案屬性的「C/C++」頁(yè)面中,選擇「程式碼產(chǎn)生」(Code Generation) 子頁(yè)面,其中有個(gè)「執(zhí)行階段程式庫(kù)」(Runtime Library) 的項(xiàng)目,也就是專案中用來(lái)設(shè)定 CRT 連結(jié)版本的地方。其中總共有四個(gè)選項(xiàng),正好對(duì)應(yīng)於上述靜態(tài)連結(jié)與動(dòng)態(tài)連結(jié)的四個(gè)不同程式庫(kù)版本。
如果你沒(méi)有做任何設(shè)定就開(kāi)始建置程式的話,MSVC 的預(yù)設(shè)選項(xiàng)則會(huì)使用動(dòng)態(tài)連結(jié)的版本。 ![]() C Runtime Library 請(qǐng)注意,以上只是單純 C 語(yǔ)言的程式庫(kù)而沒(méi)有包含 C++ 語(yǔ)言在內(nèi)。如果你的程式系統(tǒng)中,有包含 C++ 語(yǔ)言的程式碼的話,那又是另外一回事了。但是在專案屬性的頁(yè)面中,為什麼找不到相關(guān)的設(shè)定選項(xiàng)呢?因?yàn)?MSVC 悄悄地幫程式設(shè)計(jì)者代勞處理掉了。只要在程式碼中使用 #include 語(yǔ)法納入任何一個(gè) C++ 的標(biāo)頭檔,例如 iostream 或 fstream,MSVC 就會(huì)在連結(jié)器的運(yùn)作階段中,自動(dòng)幫我們連結(jié) C++ 的執(zhí)行階段程式庫(kù)。而 C++ 的執(zhí)行階段程式庫(kù),同樣可分為四個(gè)版本:
至於程式執(zhí)行檔使用的是靜態(tài)連結(jié)或者動(dòng)態(tài)連結(jié)的版本,就仰賴於 C 語(yǔ)言的版本設(shè)定選項(xiàng)了。舉個(gè)例子來(lái)說(shuō),如果你撰寫(xiě)了一個(gè) Debug 組態(tài)的 C++ 程式,並且保留專案原先預(yù)設(shè)的建置選項(xiàng)(動(dòng)態(tài)連結(jié)),那麼最終建置出來(lái)的程式執(zhí)行檔將會(huì)相依於 MSVCR90D.dll 以及 MSVCP90D.dll 兩個(gè) DLL 檔案。如果將相同的程式以 Release 組態(tài)建置完成,則會(huì)相依於 MSVCR90.dll 以及 MSVCP90.dll 二者。 ![]() Standard C++ Library 剛學(xué)習(xí)程式設(shè)計(jì)的入門(mén)者,經(jīng)常會(huì)在滿心歡喜地完成一件程式作品並且傳給其他人使用時(shí),卻發(fā)現(xiàn)不能在別人的電腦上啟動(dòng)程式,其實(shí)就是陷入了使用者電腦缺少 DLL 檔案而無(wú)法執(zhí)行程式的窘境。有三種方法可以解決這個(gè)令人困擾的問(wèn)題:
當(dāng)你無(wú)法確定自己的程式或別人的程式,是否相依於某些特定的 DLL 檔案時(shí),有一個(gè)非常好用的免費(fèi)工具程式 Dependency Walker,可以開(kāi)啟 EXE 格式的執(zhí)行檔或者 DLL 格式的動(dòng)態(tài)程式庫(kù),然後詳細(xì)地條列出它們所相依的 DLL 檔案。 瞭解了幾種不同的 CRT 版本選項(xiàng)之後,回到最前面的錯(cuò)誤訊息問(wèn)題,相信各位現(xiàn)在應(yīng)該能夠很清楚地理解,原來(lái)會(huì)發(fā)生這些奇怪的錯(cuò)誤狀況,是因?yàn)槌淌酵瑫r(shí)連結(jié)了 LIBCMTD.lib 與 MSVCRTD.lib 所以造成函式定義版本衝突。也就是說(shuō),程式連結(jié)器已經(jīng)在其中一個(gè) CRT 的版本中找到所需的函式定義,但此時(shí)卻又跳出另外一位 CRT,也給了一份相同函式的實(shí)作版本,所以連結(jié)器無(wú)法判斷應(yīng)該忽略誰(shuí)並且選擇誰(shuí)。 而這個(gè)狀況的發(fā)生原因,就是你的程式與程式所連結(jié)的外部程式庫(kù),使用了不同的 CRT 版本之故。例如,當(dāng)你的程式使用了 Lua,自然必須連結(jié)至 Lua 的程式庫(kù) lua5.1.lib,但如果 lua5.1.lib 是以靜態(tài)連結(jié)版本的 CRT 建置而成,而你的程式卻是以預(yù)設(shè)選項(xiàng),動(dòng)態(tài)連結(jié) CRT 來(lái)建置程式執(zhí)行檔的話,如此一來(lái)就會(huì)產(chǎn)生上述這些錯(cuò)誤訊息了。至此,問(wèn)題的答案已昭然若揭,解決方法有二種:其一是將 Lua 重新以動(dòng)態(tài)連結(jié) CRT 的方式建置出一個(gè)新的程式庫(kù),其二則是將自己的程式專案改成以靜態(tài)連結(jié) CRT 方式建置。 換個(gè)角度想,當(dāng)你身為一位程式庫(kù)的設(shè)計(jì)開(kāi)發(fā)者,想要將自己寫(xiě)的東西分享給其他人,但又不想要完全開(kāi)放自己撰寫(xiě)的程式源碼時(shí),至少可以同時(shí)提供以下四種版本的程式庫(kù),以妥善滿足使用者的各種不同需求:
然而,有時(shí)候世界並不會(huì)運(yùn)作得如此理想。在某些特殊的狀況下,當(dāng)我們使用他人所寫(xiě)的第三方程式庫(kù)時(shí),有時(shí)可能只拿得到其中某個(gè)特定的版本,例如 Release_Static 版本時(shí),就很有可能會(huì)遇到程式庫(kù)衝突的錯(cuò)誤情形。此時(shí)就需要視專案的實(shí)際需求而定,可以在專案屬性中指定「忽略特定程式庫(kù)」(Ignore Specific Library) 這個(gè)選項(xiàng),讓程式碼連結(jié)器忽略某些程式庫(kù),以此化解動(dòng)靜程式庫(kù)或新舊程式庫(kù)之間的恩怨衝突。 小測(cè)驗(yàn):你所撰寫(xiě)的程式,必須連結(jié)某個(gè)以靜態(tài)多執(zhí)行緒 (/MT) CRT 建置而成的程式庫(kù)。如果你的程式在 Debug 組態(tài)下以多執(zhí)行緒偵錯(cuò) (/MTd) 選項(xiàng)建置,是否會(huì)產(chǎn)生衝突?如果你的程式在 Release 組態(tài)下以多執(zhí)行緒 (/MT) 選項(xiàng)建置,是否會(huì)產(chǎn)生衝突?是的話,應(yīng)該如何解決? 延伸閱讀:
|
|
來(lái)自: win2zhang > 《vs調(diào)試》