五大標準調試宏
| 宏名 | 類型 | 含義 | 示例輸出 |
__FILE__ | 字符串字面量 | 當前源文件路徑 | "/home/user/project/main.c" |
__LINE__ | 整數常量 | 當前行號 | 42 |
__func__ | 字符串字面量 (C99) | 當前函數名 | "calculate_sum" |
__DATE__ | 字符串字面量 | 編譯日期 | "Jan 15 2025" |
__TIME__ | 字符串字面量 | 編譯時間 | "14:30:00" |
實戰:構建專業級日誌宏
// log.h
#define LOG(fmt, ...) \
fprintf(stderr, "[%s:%d %s()] " fmt "\n", \
__FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define ASSERT(cond, fmt, ...) \
do { \
if (!(cond)) { \
fprintf(stderr, "[ASSERT FAILED %s:%d %s()] " fmt "\n", \
__FILE__, __LINE__, __func__, ##__VA_ARGS__); \
abort(); \
} \
} while(0)
// 使用示例
void process(int fd) {
LOG("Processing fd=%d", fd);
ASSERT(fd >= 0, "Invalid file descriptor");
// ...
}
編譯器擴展宏 (GCC/Clang/MSVC)
| 宏 | 編譯器 | 含義 |
__PRETTY_FUNCTION__ | GCC/Clang | 帶簽名的函數名 (模板/重載友好) |
__FUNCTION__ | GCC/MSVC | 同__func__,非標準 |
__COUNTER__ | GCC/MSVC | 單調遞增整數,生成唯一標識符 |
__BASE_FILE__ | GCC | 主源文件 (非頭文件) |
__INCLUDE_LEVEL__ | GCC | 頭文件嵌套深度 |
進階技巧:字符串化與拼接
// 字符串化 #
#define STR(x) #x
#define XSTR(x) STR(x)
// 拼接 ##
#define CONCAT(a, b) a##b
#define UNIQUE_NAME(base) CONCAT(base, __COUNTER__)
// 實際應用:自動註冊測試用例
#define TEST_CASE(name) \
static void UNIQUE_NAME(test_)(); \
struct UNIQUE_NAME(Registrar_) { \
UNIQUE_NAME(Registrar_)() { \
test_registry_add(XSTR(name), UNIQUE_NAME(test_)); \
} \
} UNIQUE_NAME(reg_); \
static void UNIQUE_NAME(test_)()
// 使用
TEST_CASE("string_reverse") {
ASSERT(strcmp(reverse("abc"), "cba") == 0);
}
調試構建最佳實踐
- 編譯選項:
-g -O0 -DDEBUG -fno-omit-frame-pointer
- 斷言策略:
NDEBUG控制assert(),自定義ASSERT釋構版保留
- 地址消毒器:
-fsanitize=address,undefined 捕獲UB
- 靜態分析:
clang-tidy, cppcheck, Coverity
- 棧追蹤:
backtrace() + dladdr() 獲取運行時調用棧
常見誤區
- __func__ 是變量:實為
static const char __func__[] = "name";,不能用sizeof(__func__)獲取長度 (需strlen)
- __DATE__/__TIME__ 觸發重編譯:每次構建時間變,導致增量構建失效,發布版建議固定或移除
- VC6不支持__func__:用
__FUNCTION__或升級編譯器 (VS2013+ 完整支持C99)
總結
__FILE__, __LINE__, __func__ 組成了編譯期的「時空座標系」。善用它們構建日誌、斷言、測試註冊、錯誤上下文捕獲,能讓調試效率提升數量級。記住:好的日誌是事後除錯的時光機,好的斷言是運行時的守護神。