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