官术网_书友最值得收藏!

3.1.1 編譯的基本概念

編譯是程序開發(fā)過程中必不可少的環(huán)節(jié),是將用高級語言(例如C、C++、Java)編寫的程序轉(zhuǎn)換成計算機能夠理解和執(zhí)行的指令的過程。在編譯過程中,我們不僅可以檢查程序中的錯誤,還能夠優(yōu)化代碼以提高程序的執(zhí)行效率。將程序編譯成可執(zhí)行文件的過程可以分為4個步驟:預(yù)處理、編譯、匯編和鏈接。編譯流程如圖3-1所示。

圖3-1 編譯流程

接下來我們以將hello.c文件編譯成可執(zhí)行文件為例,對編譯的過程進行介紹。前提是需要安裝GCC,在Loongnix操作系統(tǒng)中,推薦直接安裝“build-essential”,這一操作將一并安裝編譯所需的許多工具,在終端中執(zhí)行以下命令即可。

sudo apt install build-essential

hello.c文件中的內(nèi)容如下。

1. #include <stdio.h>
2. #define str "world."
3. int main()
4. {
5.     //這是注釋
6.     printf("hello %s\n",str);
7.     return 0;
8. }

預(yù)處理是編譯過程的第一步,這是一個可選步驟,可以根據(jù)需要選擇執(zhí)行或不執(zhí)行。在預(yù)處理階段,編譯器會對程序進行預(yù)處理,包括對頭文件進行解析、宏替換、條件編譯等操作。預(yù)處理生成的是完整的C程序。在命令行中執(zhí)行以下命令,對hello.c文件進行預(yù)處理,得到hello.i文件。

gcc -o hello.i -E hello.c

此過程中,預(yù)處理器會將包含頭文件的具體內(nèi)容讀取到文本中來替換“#include<stdio.h>”,同時刪除所有注釋,將所有的“#define”刪除,并展開所有的宏定義,生成hello.i文件,示例如下(左邊為hello.c文件,右邊為hello.i文件)。

可以看到原本的hello.c文件中只有8行代碼,而現(xiàn)在的hello.i文件中有732行代碼,并且注釋被刪掉了,變量“str”被替換為“world.”,并刪除了“#define”。

在預(yù)處理過程中具體進行如下操作。

① 將所有的#define刪除,并展開所有的宏定義。

② 處理所有的預(yù)編譯指令,例如#if、#elif、#else、#endi等。

③ 處理#include預(yù)編譯指令,將被包含的文件插入預(yù)編譯指令的位置。

④ 添加行號信息、文件名標識,以便于調(diào)試。

⑤ 刪除所有的注釋。

⑥ 保留所有的#pragma編譯指令,因為在編寫程序的時候,我們經(jīng)常要用#pragma編譯指令來設(shè)定編譯的狀態(tài),或指示編譯器完成一些特定的動作。

⑦ 生成文件(去注釋、宏替換、頭文件展開),編譯生成的文件不包含任何宏定義,因為宏定義已經(jīng)被展開,并且包含的文件已經(jīng)被插入文件。

預(yù)處理完成得到hello.i文件后,進行第二步——編譯。在編譯階段,編譯器將預(yù)處理后的代碼翻譯成匯編代碼。編譯器會對代碼進行語法和語義分析,檢查代碼的正確性和合理性,并將代碼轉(zhuǎn)換為匯編語言的形式。在命令行中執(zhí)行以下命令,對hello.i文件進行編譯,得到hello.s文件。

gcc -S hello.i

hello.s文件中的內(nèi)容如圖3-2所示,可以看到hello.s文件中的內(nèi)容全是匯編代碼。

在編譯中,具體操作如下。

① 進行掃描、語法分析、語義分析、源代碼分析、目標代碼生成、目標代碼優(yōu)化。通過這些操作,源代碼將會被轉(zhuǎn)換為目標代碼。目標代碼是機器可以直接執(zhí)行的代碼,也被稱為匯編代碼。

② 生成匯編代碼。匯編代碼是匯編器所能接受的代碼形式,以機器語言的助記符形式表示,與機器語言一一對應(yīng)。

圖3-2 hello.s文件中的內(nèi)容

③ 匯總符號。在編譯過程中,全局變量和函數(shù)都有各自的符號。這些符號需要被統(tǒng)一匯總,以便在鏈接過程中進行正確的鏈接。

④ 生成hello.s文件。

完成編譯并得到hello.s文件后,進行第三步——匯編。在命令行中執(zhí)行以下命令,對hello.s文件進行匯編,得到hello.o文件,這個過程將匯編代碼翻譯成機器語言指令,把這些指令打包成一種被叫作可重定位目標程序的格式,并將結(jié)果保存在新生成的二進制文件hello.o中。

gcc -c hello.s

在匯編中,具體操作如下。

① 根據(jù)匯編代碼和特定平臺,把匯編代碼翻譯成二進制形式,以便計算機能夠理解和執(zhí)行。

② 合并各個section(操作系統(tǒng)或編程語言中的一種數(shù)據(jù)結(jié)構(gòu)),包括代碼段、數(shù)據(jù)段、BSS(未初始化數(shù)據(jù))段等,將它們組成完整的可執(zhí)行文件。

③ 合并符號,將各個源文件中的符號信息整合到同一個符號表中,以方便鏈接器進行下一步操作。

④ 生成hello.o文件,目標文件包含匯編代碼和符號表等信息。目標文件仍然不能被直接執(zhí)行,需要進行進一步的鏈接處理。

進行程序編譯成可執(zhí)行文件的最后一步——鏈接。在命令行中執(zhí)行以下命令,對hello.o文件進行鏈接,得到可執(zhí)行文件。處理可重定位文件,把各種符號引用和符號定義轉(zhuǎn)換成可執(zhí)行文件中的合適信息,通常是虛擬地址。

gcc -o hello.o

在鏈接中,具體操作如下。

① 合并各個hello.o文件的代碼段、數(shù)據(jù)段、BSS段等section,合并符號表,進行符號解析。

② 進行符號地址重定位。鏈接器會根據(jù)符號表中的符號信息,將所有的符號引用和定義進行匹配,確定每個符號的最終地址,同時在目標文件中修正每個符號的引用地址。

③ 進行代碼和數(shù)據(jù)的重定位。鏈接器會根據(jù)重定位表(relocation table)中的信息,計算出每個代碼或數(shù)據(jù)的最終地址,同時在目標文件中修正每個引用地址。

④ 生成可執(zhí)行文件。鏈接器將所有目標文件的代碼段、數(shù)據(jù)段、BSS段等組合在一起,生成可執(zhí)行文件。

鏈接操作完成后即可生成可執(zhí)行文件。但在不同操作系統(tǒng)下,可執(zhí)行文件格式并不相同。在Linux操作系統(tǒng)中,常見的可執(zhí)行文件格式為以out為擴展名的格式或者可執(zhí)行與可鏈接格式(Executable and Linkable Format,ELF),而ELF的可執(zhí)行文件是沒有擴展名的。在Windows操作系統(tǒng)中,常見的可執(zhí)行文件格式為以exe和dll為擴展名的格式。而在macOS操作系統(tǒng)中,常見的可執(zhí)行文件格式為Mach-O格式。需要注意的是,為特定操作系統(tǒng)生成的可執(zhí)行文件在其他操作系統(tǒng)中無法執(zhí)行。因此,在開發(fā)時我們需要根據(jù)目標操作系統(tǒng)選擇合適的編譯器并生成對應(yīng)格式的可執(zhí)行文件。

主站蜘蛛池模板: 皋兰县| 高雄市| 谷城县| 招远市| 霍州市| 津南区| 红河县| 马山县| 东山县| 深州市| 淮滨县| 民乐县| 陕西省| 崇文区| 兰西县| 嫩江县| 辽阳县| 栾城县| 论坛| 芷江| 封开县| 习水县| 驻马店市| 买车| 濉溪县| 凤翔县| 邹平县| 西城区| 内乡县| 海伦市| 清苑县| 逊克县| 武冈市| 彝良县| 南安市| 得荣县| 瓮安县| 清新县| 鄂尔多斯市| 宁陕县| 潼关县|