22 条件编译与固件宏配置
适用场景:BLE/TWS 固件多产品配置、ANC/低延迟功能裁剪。重点记录工程陷阱,不重复普通宏基础。
1. 一张图看清 #ifdef 与 #if
#define ENABLE_ANC 0
│
├── #ifdef ENABLE_ANC ── 检查“是否定义” ── 真(保留代码)
│
└── #if ENABLE_ANC ───── 检查“值是否非零” ── 假(排除代码)
| 写法 | #define ENABLE_ANC 0 | #define ENABLE_ANC 1 | 未定义 |
|---|---|---|---|
#ifdef ENABLE_ANC | 真 | 真 | 假 |
#if ENABLE_ANC | 假 | 真 | 假(一般未定义标识符按 0) |
结论:宏定义为 0,仍然属于“已定义”。 采用 0/1 的产品功能开关通常使用 #if ENABLE_ANC。
2. 未定义宏可能静默关闭功能
#define ENABLE_ANC 1
#if ENABLE_ACN // 拼写错误!ACN 未定义,在此按 0 处理
anc_init();
#endif
#if 中未定义的普通标识符通常按 0 处理,因此拼错宏名可能不报错,却使功能被排除。若配置是必填项,应主动检查:
#ifndef ENABLE_ANC
#error "ENABLE_ANC must be configured"
#endif
工程补充:编译器可提供对未定义宏用于
#if的告警(例如 GCC/Clang 的-Wundef);是否开启由项目构建配置决定。
3. 产品配置与公共默认值:顺序很重要
产品配置先处理 公共默认配置后处理
#define ENABLE_ANC 0 ──→ #ifndef ENABLE_ANC
#define ENABLE_ANC 1
#endif
│
▼
保留产品值 0
// product_config.h 已先被包含,定义 ENABLE_ANC 为 0
#ifndef ENABLE_ANC
#define ENABLE_ANC 1
#endif
| 写法 | 优点 | 风险 |
|---|---|---|
无条件 #define ENABLE_ANC 1 | 简单 | 与已有不同定义冲突,需诊断 |
#ifndef 后定义默认值 | 保留此前的产品配置 | 必须保证产品配置先被预处理 |
不是“后面的定义总能覆盖前面的定义”。 如确需改变宏值,应遵循项目约定明确处理,例如先 #undef 后重新定义。
4. #if 与运行时 if:发生在不同阶段
源代码
│
▼
预处理:#if 宏表达式 ──→ 排除不满足条件的代码
│
▼
编译 / 链接
│
▼
运行:if (变量) ──────→ 根据当时变量值选择执行路径
#include <stdio.h>
#define TWS_MODE 1
int main(void)
{
int ble_connected = 1;
#if TWS_MODE
puts("TWS");
#endif
#if ble_connected // 不是宏,在 #if 中按 0 处理
puts("BLE A");
#endif
if (ble_connected) // 是 C 局部变量,运行时判断
puts("BLE B");
return 0;
}
输出:
TWS
BLE B
| 对比 | #if | if |
|---|---|---|
| 判断阶段 | 预处理 | C 程序运行语义 |
| 可以读取局部变量 | 否 | 是 |
| 典型用途 | 固件功能裁剪、不同芯片编译配置 | 连接状态、用户模式、当前设备状态 |
| 对最终 Flash 的影响 | 排除的代码不参与编译 | 编译器也可能优化恒假分支,不能断言一定占 Flash |
5. BLE/TWS 工程速记
#ifdef看有没有定义;#if看数值是否为真。- 未定义宏在
#if中可能静默按 0 处理;必填配置用#error检查。 - 先处理产品配置,再用
#ifndef提供默认值。 #if决定是否编译;if决定是否执行。- 产品功能开关用构建配置;连接状态等实时变量用运行时判断。
一句话:产品能力在编译期裁剪,设备状态在运行时判断。