- Linux網(wǎng)絡(luò)程序設(shè)計:基于龍芯平臺
- 趙洪 李兆斌 魏占禎編著
- 2070字
- 2024-04-24 17:35:23
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í)行文件。
- ClickHouse性能之巔:從架構(gòu)設(shè)計解讀性能之謎
- Apache ZooKeeper Essentials
- Java技術(shù)手冊(原書第7版)
- 新編Premiere Pro CC從入門到精通
- CouchDB and PHP Web Development Beginner’s Guide
- Hands-On Automation Testing with Java for Beginners
- Python Data Analysis Cookbook
- Getting Started with LLVM Core Libraries
- Nginx Lua開發(fā)實戰(zhàn)
- HTML5從入門到精通(第4版)
- Python機器學(xué)習(xí):預(yù)測分析核心算法
- Unity&VR游戲美術(shù)設(shè)計實戰(zhàn)
- Python編程:從入門到實踐(第3版)
- OpenStack Networking Essentials
- Spring 5 Design Patterns