2026-01-09,某些文章具有時(shí)效性,若有錯(cuò)誤或已失效,請(qǐng)?jiān)谙路?a href="#comment">留言或聯(lián)系老夜。源碼是如何變身 EXE 的?一文看懂編譯、鏈接與內(nèi)存管理
1 計(jì)算機(jī)只能運(yùn)行本地代碼
首先,請(qǐng)大家看一下代碼清單 8-1。這是一個(gè)用 C 語(yǔ)言記述的 Windows 程序。該程序運(yùn)行后,會(huì)把 123 和 456 的平均值 289.5 顯示在 消息框
?
?
?
用某種編程語(yǔ)言編寫的程序就稱為?源代碼?,保存源代碼的文件稱為?源文件。源代碼是無(wú)法直接運(yùn)行的。這是因?yàn)?,CPU 能直 接解析并運(yùn)行的不是源代碼而是本地代碼的程序。
即使是用不同編程語(yǔ)言編寫的代碼,轉(zhuǎn)換成本地 代碼后,也都變成用同一種語(yǔ)言(機(jī)器語(yǔ)言)來(lái)表示了。
?
2 本地代碼的內(nèi)容
用記事本打開(kāi)由代碼清單的內(nèi)容轉(zhuǎn)換成本地代碼得到的 EXE 文件(Sample1.exe),頁(yè)面顯示情況如圖所示。
?
接下來(lái),我們把剛才的 EXE 文件的內(nèi)容 Dump 一下。?Dump是指 把文件的內(nèi)容,每個(gè)字節(jié)用 2 位十六進(jìn)制數(shù)來(lái)表示的方式。
?
而計(jì)算機(jī)就是把所有的信息作為數(shù)值的集合來(lái)處理的。例如,A 這個(gè)字符數(shù)據(jù)就是用十六進(jìn)制數(shù) 41 來(lái)表示的。與此相同,計(jì)算機(jī)指令 也是數(shù)值的羅列。這就是本地代碼。
3 編譯器負(fù)責(zé)轉(zhuǎn)換源代碼
能夠把 C 語(yǔ)言等高級(jí)編程語(yǔ)言編寫的源代碼轉(zhuǎn)換成本地代碼的程 序稱為?編譯器?。
-
編譯器首先讀入代碼的內(nèi)容,然后再把源代碼轉(zhuǎn)換成本地代碼。 -
讀入的源代碼還要經(jīng)過(guò)語(yǔ)法解析、 句法解析、語(yǔ)義解析等,才能生成本地代碼。
根據(jù) CPU 類型的不同,本地代碼的類型也不同。因而,編譯器不僅和編程語(yǔ)言的種類有關(guān),和 CPU 的類型也是相關(guān)的。
?
-
“想要買的是何種編程語(yǔ)言用的編譯器” -
“編譯器生成的本地代碼是用于哪 種 CPU 的” -
“該編譯器是在什么環(huán)境下使用的”。
?
4 僅靠編譯是無(wú)法得到可執(zhí)行文件的
編譯器轉(zhuǎn)換源代碼后,就會(huì)生成本地文件。不過(guò),本地文件是無(wú) 法直接運(yùn)行的。為了得到可以運(yùn)行的 EXE 文件,編譯之后還需要進(jìn)行?“鏈接”處理。
把多個(gè)目標(biāo)文件結(jié)合,生成 1 個(gè) EXE 文件的處理就是鏈接,運(yùn)行 連接的程序就稱為?鏈接器 (linkage editor或連結(jié)器)。
5 啟動(dòng)及庫(kù)文件
大家可能會(huì)有這樣一個(gè)疑問(wèn):“鏈接時(shí)不指定 sprintf() 和 MessageBox() 的目標(biāo)文件也沒(méi)問(wèn)題么?”這個(gè)擔(dān)心是多余的。在鏈接的 命令行末尾,存在著擴(kuò)展名是“.lib”的 import32.lib 和 cw32.lib 這兩個(gè) 文件。這是因?yàn)?sprintf() 的目標(biāo)文件在 cw32.lib 中,MessageBox() 的目 標(biāo)文件在 import32.lib 中(實(shí)際上,MessageBox() 的目標(biāo)文件在 user32. dll 這個(gè) DLL 文件中。關(guān)于這一點(diǎn),我們會(huì)在后面進(jìn)行說(shuō)明)。
像 import32.lib 及 cw32.lib 這樣的文件稱為庫(kù)文件。?庫(kù)文件指的是 把多個(gè)目標(biāo)文件集成保存到一個(gè)文件中的形式。鏈接器指定庫(kù)文件后, 就會(huì)從中把需要的目標(biāo)文件抽取出來(lái),并同其他目標(biāo)文件結(jié)合生成 EXE 文件。
6 DLL 文件及導(dǎo)入庫(kù)
Windows 以函數(shù)的形式為應(yīng)用提供了各種功能。這些形式的函數(shù) 稱為?API(Application Programming Interface,應(yīng)用程序接口)。
Windows 中,API 的目標(biāo)文件,并不是存儲(chǔ)在通常的庫(kù)文件中,而 是存儲(chǔ)在名為?DLL(Dynamic Link Library)文件的特殊庫(kù)文件中。
我們把類似于 import32.lib 這樣的庫(kù)文件稱為?導(dǎo)入庫(kù)。與此相反,存儲(chǔ)著目標(biāo)文件的實(shí)體,并直接和 EXE 文件結(jié)合的庫(kù) 文件形式稱為?靜態(tài)鏈接庫(kù)。
至此,我們總結(jié)一下 Windows 中的編譯及鏈接機(jī)制,如圖
?
7 可執(zhí)行文件運(yùn)行時(shí)的必要條件
EXE 文件中給變量及函數(shù)分配 了虛擬的內(nèi)存地址。在程序運(yùn)行時(shí),虛擬的內(nèi)存地址會(huì)轉(zhuǎn)換成實(shí)際的 內(nèi)存地址。鏈接器會(huì)在 EXE 文件的開(kāi)頭,追加轉(zhuǎn)換內(nèi)存地址所需的必 要信息。這個(gè)信息稱為再配置信息。EXE 文件的再配置信息,就成為了變量和函數(shù)的相對(duì)地址。相對(duì) 地址表示的是相對(duì)于基點(diǎn)地址的偏移量,也就是相對(duì)距離。在源代碼中,雖然變量及函數(shù)是在 不同位置分散記述的,但在鏈接后的 EXE 文件中,變量及函數(shù)就會(huì)變 成一個(gè)連續(xù)排列的組。這樣一來(lái),各變量的內(nèi)存地址就可以用相對(duì)于 變量組起始位置這一基點(diǎn)的偏移量來(lái)表示,同樣,各函數(shù)的內(nèi)存地址 也可以用相對(duì)于函數(shù)組起始位置這一基點(diǎn)的偏移量來(lái)表示。
?
8 程序加載時(shí)會(huì)生成棧和堆
不過(guò),當(dāng)程序加載到內(nèi)存后,除此之外還會(huì)額外生成兩個(gè)組,那就是棧和堆。?棧是用來(lái)存儲(chǔ)函數(shù)內(nèi)部臨時(shí)使用的變量(局部變量),以及函數(shù)調(diào)用時(shí)所用的參數(shù)的內(nèi)存區(qū)域。?堆是用來(lái)存儲(chǔ)程序運(yùn)行時(shí)的任意數(shù)據(jù)及對(duì)象的內(nèi)存領(lǐng)域.
EXE 文件中并不存在棧及堆的組。棧和堆需要的內(nèi)存空間是在 EXE 文件加載到內(nèi)存后開(kāi)始運(yùn)行時(shí)得到分配的。
?
棧及堆的相似之處在于,他們的內(nèi)存空間都是在程序運(yùn)行時(shí)得到 申請(qǐng)分配的 .
無(wú)論是 C 語(yǔ)言還是 C++, 如果沒(méi)有在程序中明確釋放堆的內(nèi)存空間,那么即使在處理完畢后, 該內(nèi)存空間仍會(huì)一直殘留。這個(gè)現(xiàn)象稱為?內(nèi)存泄露?(memory leak)
9 有點(diǎn)難度的 Q&A
Q :編譯 和解釋 有什么不同?
A :編譯器是在運(yùn)行前對(duì)所有源代碼進(jìn)行解釋處理的。而解釋器則 是在運(yùn)行時(shí)對(duì)源代碼的內(nèi)容一行一行地進(jìn)行解釋處理的。
Q :“分割編譯”指的是什么?
A :將整個(gè)程序分為多個(gè)源代碼來(lái)編寫,然后分別進(jìn)行編譯,最后 鏈接成一個(gè) EXE 文件。這樣每個(gè)源代碼都相對(duì)變短,便于程序管理。
Q :“Build”指的是什么?
A :根據(jù)開(kāi)發(fā)工具種類的不同,有的編譯器可以通過(guò)選擇“Build” 菜單來(lái)生成 EXE 文件。這種情況下,Build 指的是連續(xù)執(zhí)行編譯和鏈接。
Q :使用 DLL 文件的好處是什么?
A :DLL 文件中的函數(shù)可以被多個(gè)程序共用。因此,借助該功能可 以節(jié)約內(nèi)存和磁盤。此外,在對(duì)函數(shù)的內(nèi)容進(jìn)行修正時(shí),還不需要重新鏈接(靜態(tài)鏈接)使用這個(gè)函數(shù)的程序 A。
Q :不鏈接導(dǎo)入庫(kù)的話就無(wú)法調(diào)用 DLL 文件中的函數(shù)嗎?
A :通過(guò)使用 LoadLibrary() 及 GetProcAddress() 這些 API,即使不 鏈接導(dǎo)入庫(kù),也可以在程序運(yùn)行時(shí)調(diào)用 DLL 文件中的函數(shù)。不過(guò)使用 導(dǎo)入庫(kù)更簡(jiǎn)單一些。
Q :“疊加鏈接”這個(gè)術(shù)語(yǔ)指的是什么?
A :將不會(huì)同時(shí)執(zhí)行的函數(shù),交替加載到同一個(gè)地址中運(yùn)行。通過(guò) 使用“疊加鏈接器”這一特殊的鏈接器即可實(shí)現(xiàn)。在計(jì)算機(jī)中配置的內(nèi) 存容量不多的 MS-DOS 時(shí)代,經(jīng)常使用疊加鏈接。
Q :和內(nèi)存管理相關(guān)的“垃圾回收機(jī)制”指的是什么呢?
A:垃圾回收機(jī)制(garbage collection)指的是對(duì)處理完畢后不再需 要的堆內(nèi)存空間的數(shù)據(jù)和對(duì)象 B 進(jìn)行清理,釋放它們所使用的內(nèi)存空 間。這里把不需要的數(shù)據(jù)比喻為了垃圾。進(jìn)行該處理時(shí),C 語(yǔ)言用的是 free() 函數(shù),C++ 用的是 delete 運(yùn)算符。在 C++ 的基礎(chǔ)上開(kāi)發(fā)出來(lái)的 Java 及 C# 這些編程語(yǔ)言中,程序運(yùn)行環(huán)境會(huì)自動(dòng)進(jìn)行垃圾回收。這樣 就可以避免由于程序員的疏忽(忘了記述內(nèi)存的釋放處理)而造成內(nèi)存 泄露了。
夜雨聆風(fēng)
