---
# System prepended metadata

title: 【C++ 筆記】前置處理器（preprocessor） - part 33
tags: [程式設計, C++, 程式語言]

---

【C++ 筆記】前置處理器（preprocessor） - part 33
===

目錄（Table of Contents）：

[TOC]

---

很感謝你點進來這篇文章。

你好，我並不是什麼 C++、程式語言的專家，所以本文若有些錯誤麻煩請各位鞭大力一點，我極需各位的指正及指導！！本系列文章的性質主要以詼諧的口吻，一派輕鬆的態度自學程式語言，如果你喜歡，麻煩留言說聲文章讚讚吧！

Introduction
---

preprocessor 是在 C / C++ 編譯前對 Source Code（`.cpp` files）進行處理的一種工具。

> It does many tasks such as including header files, conditional compilation, text substitution, removing comments, etc.
> From [GeeksForGeeks](https://www.geeksforgeeks.org/cpp/cpp-preprocessors-and-directives/)

preprocessor 可以做到如下這些事情：

1. 引入標頭檔
2. 條件編譯
3. 文本替換
4. 移除註解
5. 等等

另外也可以讓開發者去選擇說，哪些程式需要被保留（included）或是不需要被保留（excluded）的。

經 preprocessor 處理過後的程式碼，通常都被稱為是「已被展開的程式碼（expanded code）」，都由 `.i` 這個副檔名去做儲存的動作。

前置處理主要是在編譯器編譯前，生出一份乾淨的 `.c` / `.cpp` 檔案給它，方便日後處理。

我們所有的前置處理指令（directives）都由 `#` 符號作為開頭，如我們最常見的 `#include` 就是之一。然後這個指令不是 C++ 語法的原因，所以不需要加上分號以示結束。

## #include

這個指令就是把其他檔案的內容包含到目前的檔案之中。

常見的就是我們把 `iostream` 這個 header file 標頭檔包含到目前檔案中。

Syntax :

```cpp
#include <file_name>
#include "file_name"
```

用雙引號或是兩個大於小於號也可以。

## #define

這個指令常被用來定義一個巨集（marco），巨集也稱為宏（中國大陸音譯）。

巨集是由 preprocessor 執行的文字替換機制。

Syntax :

```cpp
#define macro_name value
```

如下範例，透過 `#define` 將 3.14159 這個常數用 PI 表示，PI 就是巨集，其後若出現的任何巨集都會被替換為 3.14159 常數。

```cpp=
#include <iostream>

#define PI 3.14159

using namespace std;

int main(){
    cout << PI;
    return 0;
}
```

### #define 的四種語法

前面介紹的屬於下面的常數巨集。

1. Constant Macros（常數巨集）
2. Chain Macros（鏈式巨集）
3. Macro Expressions（巨集運算式）
4. Multiline Macros（多行巨集）

### Chain Macros

簡單來說就是巨集再套一個巨集。

它的具體定義如下：

```cpp=
#define MACRO1_NAME  value1
#define value1  final_value
```

範例：

```cpp=
#include <iostream>

#define MA MB
#define MB MC
#define MC MD
#define MD 100

using namespace std;

int main(){
    cout << "MA : " << MA << endl;
    cout << "MB : " << MB << endl;
    cout << "MC : " << MC << endl;
    cout << "MD : " << MD << endl;
    return 0;
}
```

這支程式碼的作用主要就是 MA MB MC MD 這些巨集都可以代表 100 的意思。

可以無限串下去，只要 preprocessor 處理得來的話。

### Macro Expressions

也稱為類函數巨集。

通常可接受參數並展開成一段運算式或是函數呼叫的程式碼片段。

Syntax :

```cpp
#define MACRO_NAME (expression within brackets)
```

or 

```cpp
#define MACRO_NAME(parameters)  (expression)
```

範例：

```cpp=
#include <bits/stdc++.h>

#define add(a, b) a + b

using namespace std;

int main(){
    int a, b;
    cin >> a >> b;
    cout << add(a, b) << endl;
    return 0;
}
```

然後也可以寫成這樣：

```cpp=
#include <bits/stdc++.h>

#define add a + b

using namespace std;

int main(){
    int a, b;
    cin >> a >> b;
    cout << add << endl;
    return 0;
}
```

### Multiline Macros

就是可以建立多行的巨集。

這部分要用到反斜線 `\` 去做到這件事。

定義如下：

```cpp=
#define MACRO_NAME value \
                                            value2 \
                                            value3 \
```

範例：

```cpp=
#include <bits/stdc++.h>

#define SWAP(a, b)   \
{                   \
  int tmp = a;      \
  a = b;            \
  b = tmp;          \
}


using namespace std;

int main(){
    int a, b;
    cin >> a >> b;
    cout << "a = " << a << ", b = " << b << endl;
    SWAP(a, b);
    cout << "a = " << a << ", b = " << b << endl;
    return 0;
}
```

## #undef

這指令用於取消先前用 `#define` 定義的巨集，使用方法也很簡單，如下：

```
#undef macro_name
```

範例：

```cpp=
#include <iostream>

#define PI 3.14159

#undef PI

using namespace std;

int main(){
    cout << PI;
    return 0;
}
```

輸出結果：

```
main.cpp: In function ‘int main()’:
main.cpp:10:13: error: ‘PI’ was not declared in this scope
   10 |     cout << PI;
      |             ^~
```

## 條件編譯（Conditional Compilation）

以下這些指令都是條件預處理器指令：

- `#if`
- `#elif`
- `#else`
- `#endif`
- `#error`

Syntax :

```cpp=
#if constant_expr
    // Code to be executed if constant_expression is true
#elif another_constant_expr
    // Code to be excuted if another_constant_expression is true
#else
    // Code to be excuted if none of the above conditions are true
#endif
```

使用上與 C++ 的條件語句是差不多的，`#elif` 就是 `else if`，差別在於需要用 `#endif` 表示條件編譯的結束。

範例（https://www.geeksforgeeks.org/cpp/cpp-preprocessors-and-directives/）：

```cpp=
#include <iostream>
using namespace std;
#define PI 3.14159

int main() {

// Conditional compilation
#if defined(PI)
    cout << "PI is defined";
#elif defined(SQUARE)
    cout << "PI is not defined";
#else
#error "Neither PI nor SQUARE is defined"
#endif
    
    return 0;
}
```

輸出結果：

```
PI is defined
```

### #ifdef & #ifndef

`#ifdef` 判斷巨集是否定義；`#ifndef` 有個 n，表示判斷巨集是否未定義。

Syntax :

```cpp=
#ifdef macro_name
    // Code to be executed if macro_name is defined
#ifndef macro_name
   // Code to be executed if macro_name is not defined
#endif
```

範例：

```cpp=
#include <iostream>

using namespace std;

#define DEBUG

int main() {

    #ifdef DEBUG
        printf("除錯模式\n");
    #endif
    
    #ifndef RELEASE
        #define RELEASE
        printf("發佈模式\n");
    #endif
    
    return 0;
}
```

輸出結果：

```
除錯模式
發佈模式
```



### 為什麼需要條件編譯？

在編譯前，可以選擇性包含或排除某些程式碼，使得編譯器只編譯所需部分的程式碼，這叫做條件編譯。

這樣做有什麼好處？

1. 跨平台（Cross-platform）兼容性佳
2. 節省資源
3. 減小可執行檔（.exe）體積

## #error

用來自訂編譯錯誤時的訊息。

Syntax :

```
#error error_message
```

範例改自 GeeksForGeeks：

```cpp=
#include <iostream>
using namespace std;

// not defining PI here
// #define PI 3.14159

int main() {

    #if defined(PI)
        cout << "巨集 PI 已被定義" << endl;
    #else
        #error "巨集 PI 和 SQUARE 都沒被定義"
    #endif

    return 0;
}
```

輸出結果：

```
main.cpp:12:10: error: #error "巨集 PI 或 SQUARE 沒被定義"
   12 |         #error "巨集 PI 或 SQUARE 沒被定義"
      |          ^~~~~
```

## #warning

在編譯前可以透過這個指令自訂先行警告訊息。

跟 `#error` 有什麼差別？`#error` 是自訂編譯錯誤時的訊息，`#warning` 會在編譯前警告。

Syntax : 

```
#warning message
```

範例：

```cpp=
#include <iostream>

using namespace std;

#ifndef PI
    #warning "巨集 PI 未被定義!"
#endif

int main(){
    cout << "The Program is running now.";
    return 0;
}
```

輸出結果：

![image](https://hackmd.io/_uploads/r1q49OF5ee.png)

需注意的是，警告訊息會由編譯器不同而有所不一樣的格式。

## #pragma

`#pragma` 用於告訴編譯器針對特定需求執行特殊行為，其語法和功能並不屬於 C++ 語言標準，而是由各編譯器自行定義和支援。

在使用上需要看編譯器怎麼定義 `#pragma` 的行為，不然會因為不相容問題產生一些錯誤。

Syntax :

```
#pragma directive
```

以下是 `#pragma` 常用的 Flags：

- `#pragma once`：用於保護 header files
- `#pragma message`：用於在編譯期間打印自訂訊息
- `#pragma warning`：用於控制警告行為（如啟用或停用警告）。
- `#pragma optimize`：用於控制最佳化設定（管理最佳化等級）。
- `#pragma comment`：用於在 .o 檔中含一些附加資訊（或指定 linker 選項）。

範例：

`#pragma once` 在以下範例中，就是要確保 `MyHeader.h` 不要被多次包含，只要包含一次就好。

```cpp=
// MyHeader.h
#pragma once

struct MyStruct {
    int value;
};
```

`#pragma message` 範例：

```cpp=
#pragma message("這是編譯期間的提示訊息")

int main() {
    return 0;
}
```

`#pragma warning` 用來開啟或關閉指定的編譯器警告（僅部分編譯器支援），以下是舉 MSVC 編譯器為例製作的範例：

```cpp=
#pragma warning(disable:4100)

void func(int unusedParam) {
    // 不會出現未使用參數的警告
}
```

要重新啟用就可這樣打：`#pragma warning(default:4100)`

`#pragma optimize` 範例（MSVC）：

```cpp=
#pragma optimize("g", on)  // g: 全優化

int foo() {
    int a = 1;
    int b = 2;
    return a + b;
}

#pragma optimize("", on)  // 還原到預設優化設定
```

## # 和 ## 運算子

`#` 運算子拿來字串化，就是把巨集中的參數轉字串。

範例：

```cpp=
#include <iostream>

#define STR(x) #x

using namespace std;

int main(){
    cout << STR(Hello World);
    return 0;
}
```

輸出結果：

```
Hello World
```

以上範例的 STR 巨集透過 `#x`，可將輸入的引數 Hello World 變為字串 `"Hello World"`。

---

`##` 運算子則是把巨集中的兩個參數連接（concatenate）成一個識別字。

範例：

```cpp=
#include <iostream>

#define CONCAT(x, y) x ## y

using namespace std;

int main(){
    int CONCAT(var, 1) = 123;
    cout << var1 << endl;
    return 0;
}
```

輸出結果：

```
123
```

可以看到真的可以拿來作為識別字，這個在對於生成函數跟變數名稱會很有用。

總結
---

### 前置處理器作用

用於在編譯器處理之前，先對程式碼進行「展開與清理」。

功能：

1. 引入標頭檔
2. 條件編譯
3. 文字替換
4. 移除註解等

前置處理後的程式碼稱為「展開後程式碼」，通常存為 `.i` 檔案。

前置處理指令以 `#` 開頭，非 C++ 語法，不需要分號。

### 常見指令與功能

`#include`：引入其他檔案（常用於 header files）。

`#define`：定義巨集（文字替換），包含：

#### 常數巨集

1. 鏈式巨集（巨集套巨集）
2. 巨集運算式（類函數巨集，可傳參數）
3. 多行巨集（可用反斜線延伸多行）

`#undef`：取消已定義的巨集。

### 條件編譯

指令：`#if`、`#elif`、`#else`、`#endif`、`#ifdef`、`#ifndef`。

透過根據巨集是否定義，或條件是否成立，來選擇性保留或排除程式碼。

優點：

1. 跨平台兼容
2. 節省資源
3. 縮小可執行檔體積

`#error`：自訂錯誤訊息，強制中斷編譯。

`#warning`：在編譯前顯示警告訊息。

`#pragma`：提供編譯器專屬的特殊指令，非標準 C++。

常用 flags：

`#pragma once`：避免標頭檔重覆引入。

`#pragma message`：編譯期間顯示提示訊息。

`#pragma warning`：控制警告行為。

`#pragma optimize`：調整最佳化設定。

### 運算子

`#`：字串化，把巨集參數轉成字串。

`##`：連接參數，用於生成識別字（如動態生成變數或函數名稱）。

參考資料
---

[你所不知道的 C 語言：前置處理器應用篇 - HackMD](https://hackmd.io/@sysprog/c-preprocessor)

[[C 語言] 程式設計教學：如何使用巨集 (macro) 或前置處理器 (Preprocessor) | 開源技術教學](https://opensourcedoc.com/c-programming/preprocessor/)

[C++ Preprocessor And Preprocessor Directives - GeeksforGeeks](https://www.geeksforgeeks.org/cpp/cpp-preprocessors-and-directives/)

[#define in C++ - GeeksforGeeks](https://www.geeksforgeeks.org/cpp/define-preprocessor-in-cpp/)

[C++ 预处理器 | 菜鸟教程](https://www.runoob.com/cplusplus/cpp-preprocessor.html)