有条件编译
有条件编译用于在预处理阶段按条件选择代码片段,常见场景是平台差异、特性开关和头文件包含保护。
1. defined 运算符
defined 标识符 或 defined(标识符) 会在预处理表达式中产生 1 或 0,用于判断宏是否已定义。
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 202311L)
#define USE_C23 1
#else
#define USE_C23 0
#endif2
3
4
5
运行结果:该代码块主要用于语法或结构说明,单独运行通常无终端输出。
2. 条件指令族
#if、#elif、#else、#endif 构成通用分支;#ifdef 与 #ifndef 是“只检查是否定义”的简写;C23 增加了 #elifdef 与 #elifndef,可让多分支条件更紧凑。若目标仍是 C11/C17,可写成 #elif defined(...) 以保持兼容。
#ifdef _WIN32
#define PATH_SEP "\\"
#elifdef __unix__
#define PATH_SEP "/"
#else
#define PATH_SEP "/"
#endif2
3
4
5
6
7
运行结果:该代码块主要用于语法或结构说明,单独运行通常无终端输出。
3. 包含保护
头文件通常使用 #ifndef 保护,防止同一翻译单元重复包含:
#ifndef MY_LIB_H
#define MY_LIB_H
/* 声明 */
#endif2
3
4
5
6
运行结果:该代码块主要用于语法或结构说明,单独运行通常无终端输出。
包含保护不是可选装饰,而是头文件最基础的正确性要求。
4. 条件表达式的语义边界
#if 里的表达式发生在预处理阶段,只能基于宏展开后的常量表达式计算,不能直接读取运行期对象值。把预处理条件和运行期 if 区分清楚,能避免“为什么这段分支没有生效”的常见误判。
#define FEATURE_FLAG 1
#if FEATURE_FLAG
/* 编译期保留 */
#endif2
3
4
5
运行结果:该代码块主要用于语法或结构说明,单独运行通常无终端输出。
当条件依赖外部构建参数时,建议集中在少量配置头中定义,再由业务代码读取统一宏,避免条件分支散落到各处。
5. 特性宏命名约定
条件编译宏建议使用统一前缀并明确语义层级,例如“平台能力”“库能力”“产品开关”分开命名。这样可以减少同名宏在不同模块中的语义漂移,也方便排查某个分支为何被启用。
6. 条件分支最小化原则
同一功能若出现多层嵌套条件编译,阅读和测试成本会快速上升。更稳妥的方式是把平台差异收敛到少量适配头,再向业务层提供统一宏入口,让主要逻辑保持单一路径。
7. 用正向能力宏减少歧义
相比大量使用“缺失能力”条件(例如 NO_X),更推荐统一用正向能力宏表达“当前可用什么”。正向命名更容易组合,也更不容易在多层 #if 中出现双重否定。宏语义清楚后,条件分支的阅读负担会明显下降。
8. 条件块要保持结构对称
复杂条件编译里,建议让每个分支保持相似结构:同样的接口定义位置、同样的宏出口、同样的尾部收束。结构对称后,即使未来新增平台分支,也能直接按模板扩展,不必重排整段源码。这种对称性是长期维护条件编译代码的重要保障。