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
对比#ifif
判断阶段预处理C 程序运行语义
可以读取局部变量否是
典型用途固件功能裁剪、不同芯片编译配置连接状态、用户模式、当前设备状态
对最终 Flash 的影响排除的代码不参与编译编译器也可能优化恒假分支,不能断言一定占 Flash

5. BLE/TWS 工程速记

  1. #ifdef 看有没有定义;#if 看数值是否为真。
  2. 未定义宏在 #if 中可能静默按 0 处理;必填配置用 #error 检查。
  3. 先处理产品配置,再用 #ifndef 提供默认值。
  4. #if 决定是否编译;if 决定是否执行。
  5. 产品功能开关用构建配置;连接状态等实时变量用运行时判断。

一句话:产品能力在编译期裁剪,设备状态在运行时判断。