__FILE__ 默认为完整路径,可用 strrchr 截取文件名或 -fmacro-prefix-map 编译选项简化;__LINE__ 需 #define STRINGIFY(x) #x 转字符串;调试宏须用 do {...} while(0) 保证语法正确性并支持可变参数,通过 #ifdef DEBUG 控制开关。

直接用 __FILE__ 和 __LINE__ 就能打出当前文件名和行号,但裸用容易出错——比如字符串拼接失败、宽窄字符混用、宏展开顺序混乱。
为什么 __FILE__ 打出来是完整路径?怎么只留文件名?
编译器默认把源文件的绝对路径塞进 __FILE__,调试时太长还干扰阅读。得靠预处理器“截断”:
- 用
#define BASENAME(x) (strrchr(x, '/') ? strrchr(x, '/') + 1 : (strrchr(x, '\\') ? strrchr(x, '\\') + 1 : x))(C 风格,需包含) - C++17 起更稳妥:自己写个
constexpr字符串切片函数,或用std::filesystem::path(__FILE__).filename().string()(注意:运行时开销,仅调试用) - Clang/GCC 支持
-fmacro-prefix-map或-fdebug-prefix-map编译选项,在预处理阶段重写路径,一劳永逸
__LINE__ 是整数,但想和字符串一起 printf 怎么办?
__LINE__ 展开后是字面整数(如 42),不能直接拼进字符串字面量。常见错误写法:"line " __LINE__ —— 这会编译失败。
- 必须用
#操作符转成字符串:#define STRINGIFY(x) #x,再STRINGIFY(__LINE__) - 组合使用示例:
fprintf(stderr, "%s:%s ", BASENAME(__FILE__), STRINGIFY(__LINE__)) - 注意:
STRINGIFY(__LINE__)在宏里展开两次才生效,如果封装太深(比如套两层宏),可能得到 "__LINE__" 字面而不是数字,要小心宏展开层级
封装成调试宏时,do {...} while(0) 为什么不能少?
很多人写 #define LOG() printf(...),结果在 if 分支里出问题:
立即学习“C++免费学习笔记(深入)”;
if (cond)
LOG(); // 实际展开为 printf(...) ;
else
... // 这个 else 会找不到匹配的 if
- 必须包在
do { ... } while(0)里,保证语法上是单条语句 - 还要加
\\换行,否则宏定义会被截断 - 推荐写法:
#define DEBUG_LOG() do { fprintf(stderr, "[%s:%d] ", BASENAME(__FILE__), __LINE__); } while(0) - 如果想支持可变参数(如
DEBUG_LOG("err=%d", err)),得用...和__VA_ARGS__,GCC/Clang 支持,MSVC 需/Zc:preprocessor
Release 版本怎么自动关掉这些调试输出?
别手动删宏,用条件编译最干净:
- 定义开关:
#ifdef DEBUG包裹整个宏体,发布前不定义DEBUG - 更省事:用
#if defined(__GNUC__) || defined(__clang__)区分编译器特性(比如__builtin_expect提示分支预测) - 注意:
__FILE__和__LINE__本身无运行时开销,但fputs/printf有。真正要优化的是 I/O 调用本身,不是宏展开 - 进阶:用
static inline函数替代宏,让编译器决定是否内联,避免宏污染作用域
最常被忽略的是路径分隔符兼容性——Linux 用 /,Windows 用 \\,而 __FILE__ 在 MSVC 下默认给反斜杠,但某些构建系统(如 CMake + Ninja)可能统一转成正斜杠,硬编码 strrchr(x, '/') 会漏掉 Windows 原生路径。











