C 標準定義多個預定義巨集,在預處理階段自動展開為編譯時資訊。善用它們能打造專業級日誌、斷言、錯誤回報系統。本文完整整理 C99/C11 標準巨集用法與實戰範例。
| 巨集 | 類型 | 展開內容 | 範例 | 標準 |
|---|---|---|---|---|
__DATE__ | 字串常數 | 編譯日期 (Mmm dd yyyy) | "Feb 12 1996" | C89 |
__TIME__ | 字串常數 | 編譯時間 (hh:mm:ss) | "23:59:01" | C89 |
__FILE__ | 字串常數 | 當前原始檔完整路徑 | "/usr/src/main.c" | C89 |
__LINE__ | 整數常數 | 當前行號 | 42 | C89 |
__func__ | 靜態字串變數 | 當前函式名稱 | "main" | C99 |
__FUNCTION__ | 字串常數 (GCC 擴充) | 同 __func__ | "main" | GCC |
__STDC__ | 整數常數 | 1 表示符合 ISO C | 1 | C89 |
__STDC_VERSION__ | 長整數常數 | C 標準版本 (yyyymmL) | 201710L (C18) | C99 |
__STDC_HOSTED__ | 整數常數 | 1=宿主環境, 0=獨立環境 | 1 | C99 |
#define LOG(fmt, ...) \
fprintf(stderr, "[%s:%d %s] " fmt "\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define ASSERT(cond) \
do { if (!(cond)) { \
fprintf(stderr, "ASSERT FAILED: %s:%d %s: %s\n", \
__FILE__, __LINE__, __func__, #cond); \
abort(); \
} } while(0)
void process_data(int *buf, int len) {
ASSERT(buf != NULL);
ASSERT(len > 0);
LOG("Processing %d items", len);
// ...
if (error) LOG("Error code %d at index %d", err, i);
}
[main.c:42 process_data] Processing 100 items
[main.c:47 process_data] Error code -1 at index 5
ASSERT FAILED: main.c:38 process_data: buf != NULL
#line 指令影響
#line 100 "virtual.c"
printf("%s:%d\n", __FILE__, __LINE__); // 輸出 virtual.c:100
// 下一行 __LINE__ 變為 101
#include 影響進入標頭檔時 __FILE__/__LINE__ 切換為標頭檔資訊,返回主檔後恢復 (但 __LINE__ 會 +1)。
| 巨集 | 說明 |
|---|---|
__PRETTY_FUNCTION__ | 函式完整簽名 (含類別、模板參數) |
__COUNTER__ | 遞增整數 (每次使用 +1),用於生成唯一識別碼 |
__TIMESTAMP__ | 檔案最後修改時間 |
__BASE_FILE__ | 主編譯檔案名稱 (非標頭檔) |
__INCLUDE_LEVEL__ | 標頭檔巢狀深度 |
// 僅在 C99 以上啟用特定功能
#if __STDC_VERSION__ >= 199901L
#define HAS_INLINE 1
#endif
// 僅在 GCC/Clang 使用擴充功能
#if defined(__GNUC__) || defined(__clang__)
#define UNUSED __attribute__((unused))
#else
#define UNUSED
#endif
最佳實務:永遠使用
__func__而非__FUNCTION__(標準化)、巨集參數用##__VA_ARGS__支援零參數、斷言巨集用do{...}while(0)包裹 確保分號語法正確。這些細節決定了巨集在大型專案中的穩定性。