一探·编译与连接
1.前言
對(duì)于平常應(yīng)用程序的開(kāi)發(fā),我們很少需要關(guān)注編譯與鏈接過(guò)程,因?yàn)橥ǔ5拈_(kāi)發(fā)環(huán)境都是流行的集成開(kāi)發(fā)環(huán)境,比如Visual Studio、Delphi等。他們往往將編譯與鏈接的過(guò)程放在了一起,稱為構(gòu)建(Build)。 IDE和編譯器提供的默認(rèn)配置、編譯、鏈接參數(shù)對(duì)于大部分的應(yīng)用程序開(kāi)發(fā)而言應(yīng)經(jīng)足夠使用了。但只在這樣的開(kāi)發(fā)過(guò)程我們往往會(huì)被這些復(fù)雜的集成工具所提供的強(qiáng)大功能所迷惑,很多系統(tǒng)軟件的運(yùn)行機(jī)制或機(jī)理被掩蓋,其程序的很多莫名其妙的錯(cuò)誤讓我們無(wú)所適從。此外,面對(duì)程序運(yùn)行時(shí)種種性能瓶頸我們束手無(wú)策。這是因?yàn)槲覀兺鶅H看到了問(wèn)題的現(xiàn)象,卻沒(méi)有看清問(wèn)題的本質(zhì),所有這些問(wèn)題的的本質(zhì)就是然間運(yùn)行背后的機(jī)理及支撐軟件運(yùn)行的各種平臺(tái)與工具。如果能夠深入了解這些機(jī)制,那么解決這些問(wèn)題我們就游刃有余啦。2.被掩藏了的過(guò)程是什么?
C語(yǔ)言中的經(jīng)典“HelloWorld”程序如下所示: #include<stdio.h> int main() {printf("HelloWorld\n");return 0; } 當(dāng)我們采用集成開(kāi)發(fā)環(huán)境進(jìn)行編譯時(shí),實(shí)際上可以分解為四個(gè)步驟:預(yù)處理Preprecessing)、編譯(Compilation)、匯編(Assembly)、鏈接(Linking)。利用GCC編譯,其編譯過(guò)程如下圖所示:預(yù)編譯: 首先是源代碼文件hello.cpp和相關(guān)頭文件(如stdio.h等)被編譯器cpp預(yù)編譯成一個(gè).ii文件。 預(yù)編譯過(guò)程主要處理那些源代碼文件中的以“#”開(kāi)始的預(yù)編譯指令。比如“#include”、“#define”等,主要處理規(guī)則如下: 1.將所有的“define”刪除,并且展開(kāi)所有的宏定義; 2.處理所有條件預(yù)編譯指令,比如“#if”、“#ifdef”、“elif”、“#else”、“#endif”; 3.處理“#include”預(yù)編譯指令,將被包含的文件插入到該預(yù)編譯指令的位置,注意,這個(gè)過(guò)程是遞歸進(jìn)行的。也就是說(shuō),被包含的文件可能還包含其他文件; 4.刪除所有注釋“//”和“/**/”; 5.添加行號(hào)和文件名標(biāo)識(shí),比如#2 “hello.cpp”2,以便于編譯時(shí)編譯器產(chǎn)生調(diào)試用的行號(hào)信息及用于編譯時(shí)產(chǎn)生編譯錯(cuò)誤或警告時(shí)能夠顯示行號(hào); 6.保留所有的#pragma編譯器指令,因?yàn)榫幾g器需要使用它們; 經(jīng)過(guò)預(yù)編譯后的.ii文件不包括任何宏定義,因?yàn)樗械暮暌呀?jīng)被展開(kāi),并且包含的文件已經(jīng)被插入到.ii文件中。所以當(dāng)我們無(wú)法判斷宏定義是否正確?頭文件是否包含正確?可以查看預(yù)編譯后的文件來(lái)確定問(wèn)題。 編譯: 編譯的過(guò)程就是把與處理完的文件進(jìn)行一系列詞法分析、語(yǔ)法分析、語(yǔ)義分析、及優(yōu)化后生成相適應(yīng)的匯編代碼文件。這個(gè)過(guò)程往往是我們所說(shuō)的整個(gè)程序構(gòu)建的核心過(guò)程,也是最復(fù)雜的部分之一。這會(huì)涉及到編譯原理的一些內(nèi)容。得到的匯編輸出文件為hello.s。 匯編: 匯編器是將匯編代碼轉(zhuǎn)變成機(jī)器可以執(zhí)行的指令,每一個(gè)匯編語(yǔ)句幾乎都對(duì)應(yīng)一條機(jī)器指令。所以匯編器的匯編過(guò)程相對(duì)于編譯器來(lái)講比較簡(jiǎn)單,他沒(méi)有復(fù)雜的語(yǔ)法,也沒(méi)有語(yǔ)義,也不需要做指令優(yōu)化,只需要根據(jù)匯編指令和機(jī)器指令的對(duì)照表一一進(jìn)行翻譯就可以了,“匯編”這個(gè)名字也來(lái)源于此。 鏈接: 鏈接過(guò)程通常會(huì)讓人比較費(fèi)解,為什么匯編器不直接輸出可執(zhí)行文件,而是輸出一個(gè)目標(biāo)文件呢???鏈接過(guò)程到底包含了什么內(nèi)容???為什么要鏈接???這些問(wèn)題看似簡(jiǎn)單,實(shí)際上涉及了編譯、鏈接和庫(kù)的內(nèi)容,甚至是操作系統(tǒng)一些很底層的東西。我們僅僅記住一個(gè)事就好,可執(zhí)行文件不僅僅需要一個(gè)目標(biāo)文件(你的意圖)還需要包含系統(tǒng)的庫(kù)文件(可執(zhí)行文件是在操作系統(tǒng)上運(yùn)行的)!!!后面,我會(huì)用很大篇幅詳細(xì)說(shuō)明這個(gè)問(wèn)題。 現(xiàn)在,我們基本上知道了一個(gè)源文件到可執(zhí)行文件的過(guò)程,以及中間產(chǎn)生的臨時(shí)文件。現(xiàn)在的問(wèn)題是,編譯器到底做了什么事???
總結(jié)
- 上一篇: 不是内部或外部命令也不是可运行的程序?
- 下一篇: 二探·编译与连接