用 #ifdef 控制代码编译需先定义宏,否则无效;#ifdef 仅判断宏是否存在,判值须用 #if;宏名应加前缀防冲突,头文件卫士推荐 #pragma once 或唯一宏名。

怎么用 #ifdef 控制一段代码是否编译
直接靠 #ifdef + #endif 包住代码块,前提是前面定义过对应宏。没定义就跳过,定义了才参与编译——这是最基础也最容易漏掉“定义”这步的用法。
常见错误现象:#ifdef DEBUG_LOG 写了,但整个项目根本没 #define DEBUG_LOG,结果日志代码永远不生效,还怀疑是不是语法写错了。
- 定义宏的位置很关键:放在头文件开头、源文件顶部,或更稳妥的是通过编译器参数传入(比如
g++ -DDEBUG_LOG) - 如果只在某个 .cpp 里
#define,那它只对这个文件生效;跨文件要用头文件或编译选项 - 别用
#ifdef DEBUG_LOG == 1——#ifdef只看宏是否存在,不看值;要判值得用#if DEBUG_LOG == 1
#if 和 #ifdef 到底该选哪个
#ifdef 只检查宏是否被定义过,不管它等于几;#if 能算表达式,支持数字比较、逻辑运算,但要求宏必须是整型常量(或能展开成整型常量的宏)。
使用场景举例:你想让调试等级可配置,#define LOG_LEVEL 2,然后用 #if LOG_LEVEL >= 3 控制详细日志是否编译,这时必须用 #if。
立即学习“C++免费学习笔记(深入)”;
-
#ifdef FOO→ 安全,适合开关型标志(有/无) -
#if FOO > 0→ 要求FOO是整数宏,否则预处理器报错:error: token "FOO" is not valid in preprocessor expressions - 宏里带函数调用、字符串字面量、类型关键字?别想在
#if里用,预处理器不认识这些
怎么避免宏开关污染全局命名空间
宏没有作用域,一旦定义就一路有效到 #undef 或文件结束。多人协作时,一个模块定义了 ENABLE_CACHE,另一个模块也用同名宏但含义不同,编译可能不报错,行为却错乱。
真实踩坑案例:某 SDK 头文件里 #define ENABLE_ASYNC 1,你的代码也用了 ENABLE_ASYNC 控制本地逻辑,结果 SDK 更新后悄悄改了它的行为,你本地异步开关突然失效。
- 给宏名加前缀,比如公司/模块缩写:
MYLIB_ENABLE_ASYNC、APP_LOG_VERBOSE - 用完立刻
#undef,尤其在头文件中定义又只在局部使用的宏 - 优先考虑用
constexpr bool+if constexpr(C++17 起),它有作用域、类型安全,只是不能控制模板实例化外的代码大小
为什么 #ifndef 头文件卫士有时会失效
标准写法是 #ifndef HEADER_FOO_H → #define HEADER_FOO_H → … → #endif,但它依赖宏名唯一性。两个不同头文件用了相同宏名,第二个会被静默跳过。
更隐蔽的问题:某些构建系统(如 CMake 的 add_subdirectory)可能把同一头文件通过不同路径引入(./inc/a.h 和 ../project/inc/a.h),预处理器认为是两个文件,卫士就形同虚设。
- 宏名建议用绝对路径哈希或 UUID 片段生成,比如
INC_MYLIB_UTILS_8F3A2B_H,而不是简单拼接文件名 - CMake 中可用
set_property(GLOBAL PROPERTY USE_FOLDERS ON)配合规范路径管理,减少歧义引入 - 现代替代方案:
#pragma once大部分编译器都支持,不依赖宏名,但极少数嵌入式工具链不认
宏开关看着简单,真正难的是名字怎么起、在哪定义、什么时候清理——这些地方不动手试两轮,光看文档根本意识不到问题在哪。











