當編譯器報錯「file.c:42: error: ...」時,它是如何知道文件名與行號的?答案在於 C 標準預定義的內置宏:__FILE__ 與 __LINE__。它們不需定義即可直接使用,編譯器在預處理階段自動展開為當前源文件路徑與行號字面量。本文通過實例演示其用法與實戰價值。
| 宏名稱 | 含義 | 展開示例 |
|---|---|---|
__FILE__ | 當前源文件路徑 | "src/main.c" |
__LINE__ | 當前行號(十進制整數) | 42 |
__func__ / __FUNCTION__ | 當前函數名(C99/C++11) | "main" |
__DATE__ | 編譯日期 | "Jan 15 2025" |
__TIME__ | 編譯時間 | "14:30:00" |
__STDC__ | 是否符合標準 C | 1 |
#includevoid show() { printf("hello\n"); printf("file:%s line:%d func:%s\n", __FILE__, __LINE__, __func__); } int main() { printf("file:%s line:%d func:%s\n", __FILE__, __LINE__, __func__); show(); return 0; }
file:test.c line:13 func:main hello file:test.c line:7 func:show
將文件、行號、函數名封裝為統一日誌宏,便於發布版本條件編譯關閉。
#ifdef DEBUG
#define LOG(fmt, ...) \
printf("[DEBUG][%s:%d %s] " fmt "\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#else
#define LOG(fmt, ...) ((void)0)
#endif
// 使用示例
void process(int id) {
LOG("開始處理 id=%d", id);
// ...
LOG("處理完成");
}
GCC/Clang/MSVC 等編譯器內部同樣利用 __FILE__ / __LINE__ 宏:
file:line:col: level: message。這也是為什麼宏展開後的代碼行號可能與原始碼不一致——編譯器報告的是展開後的行號。使用 #line 指令可手動調整行號映射。
# 1. 編譯帶調試信息 gcc -g demo.c -o demo # 2. 進入 GDB gdb ./demo # 3. 查看函數地址 (gdb) info address show Symbol "show" is a function at address 0x401126. (gdb) info address main Symbol "main" is a function at address 0x401145.
內置宏是 C 語言「零開銷、編譯期計算、靜態反射」能力的典型體現。善用它們能極大提升調試效率、日誌可追蹤性與代碼可維護性。