C 语言条件编译
更新时间:2026-08-26。本文是
languages/c/主题入门层第 57 篇。同一份代码,Windows 上走 A 逻辑、Linux 上走 B 逻辑;调试时多打日志、发布时不打——不删代码、不改代码,靠编译期开关切换。这就是条件编译:让编译器替你"裁剪代码"。
本文要回答的问题
- 条件编译的指令有哪些?怎么配合?
- 它和运行时
if有什么区别? - 调试开关、平台分支怎么落地?
一、基本指令
c
#ifdef DEBUG // 若定义了 DEBUG 宏
printf("debug info\n");
#endif
#ifndef DEBUG // 若没定义 DEBUG(#ifdef 的反面)
printf("release mode\n");
#endif完整家族:#if、#ifdef、#ifndef、#elif、#else、#endif。
c
#if defined(A) && !defined(B) // #if 里可以用逻辑表达式
...
#elif defined(C)
...
#else
...
#endif#endif 必须收尾——漏掉它,后面所有代码都被吞,编译报一堆莫名其妙的错。写长条件块时建议注释标记:
c
#ifdef FOO
...
#endif /* FOO */二、与运行时 if 的本质区别
| 对比项 | 条件编译 #if | 运行时 if |
|---|---|---|
| 发生时间 | 编译前(预处理) | 程序运行 |
| 未选中的代码 | 直接不存在 | 存在但跳过 |
| 语法检查 | 不做(不编译) | 做 |
| 影响 | 二进制体积/行为 | 运行行为 |
| 用途 | 平台/配置/调试 | 逻辑分支 |
关键差异:#if 0 { 语法错误的代码 } 能编译通过——因为那段代码根本没进入编译流程。这常被用来临时注释大块代码。
三、用途 1:调试开关
c
#include <stdio.h>
#define DEBUG // 注释掉这行 = 发布模式
int main(void) {
int x = 42;
#ifdef DEBUG
printf("[DEBUG] x = %d at %s:%d\n", x, __FILE__, __LINE__);
#endif
printf("x = %d\n", x);
return 0;
}发布时只需删除 #define DEBUG 一行,所有调试日志自动消失,不需要删代码。配合 -DDEBUG 编译选项:gcc -DDEBUG main.c -o app,连源码都不用改。
四、用途 2:平台 / 环境分支
编译器预定义了一堆环境宏(gcc -dM -E - </dev/null 可查看):
c
#ifdef _WIN32
#include <windows.h>
#define PATH_SEP '\\'
#elif defined(__linux__)
#include <unistd.h>
#define PATH_SEP '/'
#else
#error "Unsupported platform!"
#endif#error 在编译期直接报错——不该出现的分支直接编译失败,把问题拦在编译期而不是运行期。
五、用途 3:头文件防重复包含
每个头文件都套上"三重奏"(也叫 include guard):
c
#ifndef MY_HEADER_H
#define MY_HEADER_H
/* 头文件内容 */
#endif /* MY_HEADER_H */作用:同一个头文件被 #include 多次时,第二次起内容整体跳过,防止重复定义。这是条件编译最常见的应用,详见头文件与 #include。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 漏 #endif | 后面代码被吞、编译错乱 | 长块加 #endif /* 标记 */ |
用 = 判断宏 | #if (DEBUG = 1) 逻辑错 | 用 == 或 defined() |
| 在 #if 里用变量 | 常量表达式错误 | #if 只能用常量/宏 |
| 忘加 include guard | 重复定义 | 三重奏模板 |
| #define 后加分号 | 展开带上分号出错 | 宏定义不加分号 |
七、与本站主线衔接
- 宏定义基础与陷阱,见宏定义 #define;
- 头文件组织与 include guard 细节,见头文件与 #include;
- 大型工程的多文件条件编译设计,见多文件工程与头文件设计。
一句话总结
条件编译(#if/#ifdef/#ifndef/#elif/#else/#endif)在编译前裁剪代码,未选中分支直接不存在——调试开关、平台分支、include guard 三大场景靠它实现,与运行时 if 是两回事——"代码还在,只是这次不编译它"。