預編譯 (Preprocessing) 是編譯流水線的第一站,由 gcc -E 或 g++ -E 觸發。它處理以 # 開頭的預處理指令,產生純 C/C++ 代碼 (無 # 指令),供下一階段編譯器處理。主要任務三類:
#include)#define/#undef)#ifdef/#ifndef/#if/#elif/#else/#endif)#include預處理器將被包含文件內容原文展開到 #include 處。示例:
// prepro.cpp
#include "prehead.h"
int main() { add(1, 2); }
// prehead.h
#ifndef _PREHEAD_H
#define _PREHEAD_H
int add(int, int);
#endif
g++ -E prepro.cpp 輸出:
int add(int, int);
int main() { add(1, 2); }
註釋被剝離,宏展開,保護宏生效防止重複包含。
#define / #undef#define PI 3.14159
#define ONE 1
#define TWO 2
int add() { return ONE + TWO; } // 預編譯後 → return 1 + 2;
弊端:調試時變量名消失 (Watch 窗口看不到 PI),無類型檢查,作用域不可控。
現代替代:const double PI = 3.14159; 或 constexpr double PI = 3.14159; (C++11)。
#define MAX(a, b) ((a) > (b) ? (a) : (b))
經典陷阱:副作用多次求值。
int a = 5, b = 0;
MAX(++a, b); // a 自增兩次!
MAX(++a, b+10); // b+10 計算兩次!
現代替代:inline 函數 / constexpr 函數 / std::max。類型安全、單次求值、可調試、支持重載。
#define DEBUG 1#ifdef _WIN32 ... #endif#x, ## (如日志宏、序列化代碼生成)| 指令 | 含義 |
|---|---|
#ifdef MACRO | 若定義了 MACRO |
#ifndef MACRO | 若未定義 MACRO |
#if EXPR | 若常量表達式 EXPR 非零 |
#elif EXPR | else if |
#else | else |
#endif | 結束塊 |
// myheader.h
#ifndef MYHEADER_H_
#define MYHEADER_H_
// 內容
#endif
命名慣例:文件名大寫 + _H_ 後綴,保證全局唯一。即使間接包含多次,預處理器保證內容僅展開一次。
// config.h (由構建腳本自動生成)
// #define PLATFORM_LINUX 1
// #define PLATFORM_WINDOWS 0
#ifdef PLATFORM_LINUX
// Linux 專用代碼
#endif
#ifdef PLATFORM_WINDOWS
// Windows 專用代碼
#endif
關鍵:配置宏由構建系統 (CMake/Autotools/Meson) 根據檢測結果生成 config.h,而非手寫。
#ifdef DEBUG
#define LOG(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)
#else
#define LOG(fmt, ...) ((void)0)
#endif
發布版定義 NDEBUG 或不定義 DEBUG,調用零開銷被優化掉。
PROJECT_MODULE_MACRO) 避免衝突do { ... } while(0) 包裹,使其像語句一樣帶分號使用,避免 if (...) MACRO; else ... 語法錯誤#define SQR(x) ((x)*(x)) 防運算符優先級錯誤inline static 函數,可讀性強、可調試、類型安全#pragma once (主流編譯器支持) 替代傳統保護宏,更簡潔Effective C++ 第三版,條款 2:盡量以 const, enum, inline 替換 #define預編譯是 C/C++ 構建系統的「元編程」層。#include 組織代碼模塊,#define 定義編譯期常量與配置,條件編譯實現多平台/多配置單源碼維護。現代 C++ 開發原則:能用語言特性 (const/constexpr/inline/template) 解決的,絕不動用宏。僅在配置隔離、代碼生成、跨平台適配等語言無能為力處,精準、克制地使用預處理器。