在 C 語言開發中,善用編譯器提供的內建宏與 C99 引入的可變參數宏,能讓除錯資訊的輸出既靈活又標準化。本文整理常用的預定義宏、C99 可變參數宏語法,並給出一個可直接用於專案的除錯宏範例。
C99 規範了幾個編譯階段就會被替換的內建宏,這些宏在除錯、日誌記錄、版本追蹤時極為實用:
__DATE__:編譯日期,格式如 "May 14 2025"__TIME__:編譯時間,格式如 "10:30:45"__FILE__:當前源檔案完整路徑__LINE__:當前原始碼行號__STDC__:若編譯器符合 C90 標準,值為 1__STDC_VERSION__:C 標準版本號,C99 為 199901L__STDC_IEC_559__ / __STDC_IEC_559_COMPLEX__:表示是否支援 IEC 60559 浮點運算與複數這些宏在預處理階段展開,不會產生執行期開銷,適合嵌入日誌或斷言訊息中。
C99 引入 __VA_ARGS__ 與 ## 運算子,解決了舊式宏無法處理可變參數的問題:
__VA_ARGS__:代表呼叫端傳入的所有可變參數##__VA_ARGS__:GNU 延伸語法,當可變參數為空時自動移除前一個逗號,避免語法錯誤#define LOG(fmt, ...) fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
呼叫 LOG("init ok") 會展開為 fprintf(stderr, "[main.c:10] init ok\n", ); —— 多餘逗號被 ## 吞掉,編譯通過。
結合條件編譯與可變參數宏,即可實現「零開銷」的發布版與「完整資訊」的除錯版:
#include <stdio.h>
#define ENABLE_DEBUG /* 發布時註解掉這一行 */
#ifdef ENABLE_DEBUG
#define DEBUG(fmt, ...) \
printf("File: %s, Line: %05d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#else
#define DEBUG(fmt, ...) ((void)0)
#endif
int main(void) {
DEBUG("System boot, version=%s", "1.2.3");
DEBUG("Memory pool initialized, blocks=%d", 1024);
return 0;
}
除錯版輸出範例:
File: main.c, Line: 00012: System boot, version=1.2.3 File: main.c, Line: 00013: Memory pool initialized, blocks=1024
發布版: 宏展開為 (void)0,編譯器最佳化後完全移除,零指令、零資料段佔用。
MYAPP_DEBUG),避免與第三方庫衝突。DEBUG、INFO、WARN、ERROR 四級,配合編譯選項 -DLOG_LEVEL=WARN 控制輸出粒度。zlog、spdlog 的 C 介面)。DEBUG("val=%d", get_val())),發布版宏為空時 get_val() 不會被呼叫,可能改變程式語意;若必須保留副作用,請改用 do { if (debug_enabled) real_log(...); } while(0) 模式。掌握內建宏與可變參數宏,讓除錯資訊「編譯期決定、執行期零負擔」,是 C 語言工程化的基礎功夫之一。