【C++ 筆記】編譯流程(Compilation Process)
【C++ 筆記】編譯流程(Compilation Process)
很感謝你點進來這篇文章。
你好,我並不是什麼 C++、程式語言的專家,所以本文若有些錯誤麻煩請各位鞭大力一點,我極需各位的指正及指導!!本系列文章的性質主要以詼諧的口吻,一派輕鬆的態度自學程式語言,如果你喜歡,麻煩留言說聲文章讚讚吧!
編譯流程(Compilation Process)
共分四階段:
- Preprocessing(前置處理)
- Compilation(編譯)
- Assembly(組譯)
- Linking(連結)

Image Source:C++ Preprocessor And Preprocessor Directives - GeeksforGeeks
所謂的 Source File 也就是 Source Code,就是 .cpp / .c 檔案。
1. Preprocessing
範例指令:g++ -E main.cpp -o main.i
Preprocessing 前置處理是 C / C++ 編譯的第一步,主要對 # 開頭的指令(稱為 marco 巨集)進行處理。
簡單來說就是把 #include 展開(把標頭檔貼進程式),處理 #define 巨集、條件編譯這些東西,最後會輸出 .i 檔,裡面含一個乾淨的 C / C++ Code 跟插入的標頭檔和展開的巨集們。
接下來會做以下步驟的處理:
- Macro Definition & Replacement
- 處理所有
#define指令,把程式中出現的巨集全用定義內容替換掉。 - 如
#define PI 3.14則將所有PI換成3.14。
- 處理所有
- File Inclusion
- 處理
#include指令,把指定的 header file 內容複製貼進 Source Code 在的位置。 - 如
#include <stdio.h>會把stdio.h內容插入開頭中,如果他放在開頭的話。
- 處理
- Conditional Compilation
- 處理
#if,#ifdef,#ifndef等條件判斷指令,符合條件的程式碼會保留,而其他的則全移除。 - 如在當只有定義
DEBUG時,#ifdef DEBUG ... #endif中的程式才會被編譯。
- 處理
- Macro Operators
- 處理
#與##等巨集運算符,#用於字串化,##用於拼接識別字。 - 如
#define STR(n) #n會生成"n"。
- 處理
- Predefined Macros
- 展開預定義巨集,如
__FILE__,__LINE__,__DATE__等,直接替換為實際內容(在預處理階段展開)。
- 展開預定義巨集,如
- Undefinition
- 處理
#undef指令,把已定義的巨集移除。 #undef PI會移除先前定義的PI。
- 處理
- Others
- 處理如
#error(強制產生錯誤),#pragma(控制編譯器行為),#line(設定行、檔名資訊等)。
- 處理如
2. Compilation
範例指令:g++ -S main.i -o main.s
Compilation 編譯為 C / C++ 編譯流程中將預處理後的 Source Code 轉換成組合語言(Assembly Language)的階段,主要在做是檢查語法、語意這些東西,然後會把高階語言翻譯成符合 CPU 架構的組合語言程式碼。
用簡單幾句話來說就是把 C/C++ Source Code 翻譯成組合語言(assembly code),順便檢查語法、型態等錯誤,最後會得到一個 .s 檔(組合語言)。
以下是 Compilation 的步驟:
Syntax Analysis(語法分析):
編譯器會根據語法規則檢查程式碼結構是否正確,如括號配對、關鍵字使用、語句結構等。Semantic Analysis(語意分析):
如未宣告變數、類型不匹配、不可操作之值運算都會在這檢查。Intermediate Representation, IR(中介碼):
編譯器會將高階語言翻譯成一種介於 Source Code 和 Assembly Language 之間的中間語言表示,以便後續優化與產生組合語言代碼。Optimization(優化):
編譯器會試著去改寫中介碼的程式碼,提升執行效率、減少不必要的運算。Assembly Code Generation:
最後編譯器會從中介碼轉換成組合語言,這時候生成的組合語言會被輸出成.s檔。
3. Assembly
範例指令:g++ -c main.cpp -o main.o
Assembly 組譯階段為 C/C++ 編譯流程中將組合語言(.s 檔)轉換成目標檔(object file,通常副檔名為 .o 或 .obj)的階段,目標檔是機器語言的二進位指令,可供連結器(Linker)使用。
這階段就只是把組合語言轉成機器碼(二進位指令),然後生成目標檔 .o 而已,然後電腦可以去執行這些低階指令,但還不能單獨去執行。
以下是 Assembly 的步驟:
讀取組合語言程式碼:
組譯器將從編譯階段輸出的組合語言程式碼(文字格式)讀入,內容是針對特定 CPU 架構設計的指令序列。指令轉換機器碼:
把組合語言指令一一轉成其對應的二進位機器指令,轉換後的程式碼 CPU 可以執行。(這步驟老實講我不知道叫啥):
組譯器會對程式中用到的符號(如變數、函數名稱)進行初步紀錄,但因為還沒去做連結的動作,具體地址會在連結階段決定。產生目標檔案(
.oor.obj):
輸出二進位格式的.oor.obj檔,內含機器碼及符號表等等,用於後續連結器將不同目標檔合併。
4. Linking
範例指令:g++ main.o math_utils.o -o main
Linking 為 C/C++ 編譯流程中的最終階段,主要將多個 object files 和連結外部函式庫(如 iostream)合併成一個可執行檔(.exe),並將程式中所有符號(函數、變數等)解析和對應到真實的記憶體地址。
比如說 add(a, b) 函數定義在另一個 math.cpp 檔案裡面,而 main.cpp 要存取 math.cpp 裡面的 add(a, b) 像下面這樣:
1 | // math.cpp |
1 | // main.cpp |
若要呼叫這個函數,首先要知道他到底在記憶體的哪裡,才能去呼叫這個函數。
(假設編譯完成)Linker 會將 main.o 裡呼叫的 add() 符號,和 math.o 裡定義的 add() 符號連起來,然後把兩個 .o 檔案合併成最終的執行檔。
總之 Linker 在做的事情就是把不同的檔案組合再一起,形成一個可執行檔,這個 .exe 檔案就是多個檔案的整體。
總結
前置處理(Preprocessing)
處理所有以 # 開頭的指令,如 #include 展開標頭檔、#define 巨集替換、條件編譯等。
會輸出純淨的 C/C++ 程式碼,存於 .i 檔。
編譯(Compilation)
檢查語法與語意正確性,將程式轉換為中介碼(IR),再優化並產生組合語言程式碼。
產出 .s 檔(Assembly code)。
組譯(Assembly)
將組合語言轉換為二進位機器碼,並生成目標檔(Object file)。
產出 .o 或 .obj 檔案,包含機器碼與符號表,但還不能單獨執行。
連結(Linking)
將多個目標檔(.o)以及相關函式庫整合,解析所有符號(如函數、變數位置)。
最終輸出可執行檔(例如 .exe)。
結語
C++ 編譯流程大致上就是這樣:
.cpp → .i → .s → .o → .exe
參考資料
[Day 3] 編譯流程 | iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天


