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

Image Source:[C++ Preprocessor And Preprocessor Directives - GeeksforGeeks](https://www.geeksforgeeks.org/cpp/cpp-preprocessors-and-directives/)
所謂的 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 跟插入的標頭檔和展開的巨集們。
接下來會做以下步驟的處理:
1. Macro Definition & Replacement
- 處理所有 `#define` 指令,把程式中出現的巨集全用定義內容替換掉。
- 如 `#define PI 3.14` 則將所有 `PI` 換成 `3.14`。
2. File Inclusion
- 處理 `#include` 指令,把指定的 header file 內容複製貼進 Source Code 在的位置。
- 如 `#include <stdio.h>` 會把 `stdio.h` 內容插入開頭中,如果他放在開頭的話。
3. Conditional Compilation
- 處理 `#if`, `#ifdef`, `#ifndef` 等條件判斷指令,符合條件的程式碼會保留,而其他的則全移除。
- 如在當只有定義 `DEBUG` 時,`#ifdef DEBUG ... #endif` 中的程式才會被編譯。
4. Macro Operators
- 處理 `#` 與 `##` 等巨集運算符,`#` 用於字串化,`##` 用於拼接識別字。
- 如 `#define STR(n) #n` 會生成 `"n"`。
5. Predefined Macros
- 展開預定義巨集,如 `__FILE__`, `__LINE__`, `__DATE__` 等,直接替換為實際內容(在預處理階段展開)。
6. Undefinition
- 處理 `#undef` 指令,把已定義的巨集移除。
- `#undef PI` 會移除先前定義的 `PI`。
7. 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 的步驟:
1. Syntax Analysis(語法分析):
編譯器會根據語法規則檢查程式碼結構是否正確,如括號配對、關鍵字使用、語句結構等。
2. Semantic Analysis(語意分析):
如未宣告變數、類型不匹配、不可操作之值運算都會在這檢查。
3. Intermediate Representation, IR(中介碼):
編譯器會將高階語言翻譯成一種介於 Source Code 和 Assembly Language 之間的中間語言表示,以便後續優化與產生組合語言代碼。
4. Optimization(優化):
編譯器會試著去改寫中介碼的程式碼,提升執行效率、減少不必要的運算。
5. Assembly Code Generation:
最後編譯器會從中介碼轉換成組合語言,這時候生成的組合語言會被輸出成 `.s` 檔。
### 3. Assembly
範例指令:`g++ -c main.cpp -o main.o`
Assembly 組譯階段為 C/C++ 編譯流程中將組合語言(`.s` 檔)轉換成目標檔(object file,通常副檔名為 `.o` 或 `.obj`)的階段,目標檔是機器語言的二進位指令,可供連結器(Linker)使用。
這階段就只是把組合語言轉成機器碼(二進位指令),然後生成目標檔 `.o` 而已,然後電腦可以去執行這些低階指令,但還不能單獨去執行。
以下是 Assembly 的步驟:
1. 讀取組合語言程式碼:
組譯器將從編譯階段輸出的組合語言程式碼(文字格式)讀入,內容是針對特定 CPU 架構設計的指令序列。
2. 指令轉換機器碼:
把組合語言指令一一轉成其對應的二進位機器指令,轉換後的程式碼 CPU 可以執行。
3. (這步驟老實講我不知道叫啥):
組譯器會對程式中用到的符號(如變數、函數名稱)進行初步紀錄,但因為還沒去做連結的動作,具體地址會在連結階段決定。
4. 產生目標檔案(`.o` or `.obj`):
輸出二進位格式的 `.o` or `.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) 像下面這樣:
```cpp=
// math.cpp
int add(int a, int b) {
return a + b;
}
```
```cpp=
// main.cpp
#include <iostream>
using namespace std;
// 宣告 add 函數(定義在 math.cpp)
int add(int a, int b);
int main() {
cout << add(3, 4) << endl;
return 0;
}
```
若要呼叫這個函數,首先要知道他到底在記憶體的哪裡,才能去呼叫這個函數。
(假設編譯完成)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`
參考資料
---
[C 概念 & 編譯 - HackMD](https://hackmd.io/@AlienHackMd/S1qP8EMmU)
[[Day 3] 編譯流程 | iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天](https://ithelp.ithome.com.tw/m/articles/10320498)
[淺談 c++ 編譯到鏈結的過程 | by Alastor Wu | Medium](https://medium.com/@alastor0325/https-medium-com-alastor0325-compilation-to-linking-c07121e2803)
[C++ Compilation process - GeeksforGeeks](https://www.geeksforgeeks.org/cpp/how-to-compile-a-cpp-program-using-gcc/)