7、#elif与#else:多分支条件编译,复杂条件表达式(defined() 运算符),实战:跨平台代码管理

条件编译这东西,说白了就是让编译器帮你“挑着看代码”。#if#elif#else 这三个家伙,跟 C 语言里的 if-else if-else 结构很像,但作用对象是代码块本身。我刚开始学的时候总觉得这玩意儿可有可无,直到有一次接手一个跨平台项目……嗯,从那以后我再也不敢小看它了。

7.1 多分支条件编译的基本语法

先看个最简单的例子。假设我们要根据不同的调试等级输出不同信息:

#define DEBUG_LEVEL 2

#if DEBUG_LEVEL == 0
    #define LOG(msg) // 啥也不做
#elif DEBUG_LEVEL == 1
    #define LOG(msg) printf("[INFO] %s\n", msg)
#elif DEBUG_LEVEL == 2
    #define LOG(msg) printf("[DEBUG] %s, line %d\n", msg, __LINE__)
#else
    #define LOG(msg) printf("[VERBOSE] %s, func: %s\n", msg, __func__)
#endif

你看,一个 #if 开头,中间可以跟任意多个 #elif,最后用 #else 兜底,#endif 收尾。编译器在预处理阶段就会根据 DEBUG_LEVEL 的值,只保留匹配的那个分支,其余的全部扔掉。

小提示:每个分支里的代码,在预处理之后就不存在了。所以不用担心条件编译影响运行效率——它影响的只是编译时间,而不是执行速度。

7.2 defined() 运算符:检查宏是否被定义

有时候我们关心的不是宏的值,而是它“有没有被定义”。这时候 defined() 就派上用场了。它返回 1(已定义)或 0(未定义)。

#if defined(__linux__)
    #include <unistd.h>
    #define PLATFORM "Linux"
#elif defined(_WIN32)
    #include <windows.h>
    #define PLATFORM "Windows"
#elif defined(__APPLE__)
    #include <TargetConditionals.h>
    #define PLATFORM "macOS"
#else
    #define PLATFORM "Unknown"
    #warning "Unknown platform, using generic settings"
#endif

我个人习惯把 defined() 和逻辑运算符组合使用,比如:

#if defined(DEBUG) && defined(VERBOSE)
    #define TRACE(fmt, ...) printf("[TRACE] " fmt "\n", ##__VA_ARGS__)
#elif defined(DEBUG)
    #define TRACE(fmt, ...) // 空定义
#else
    #define TRACE(fmt, ...) // 空定义
#endif

这里有个坑——defined() 只能用在 #if#elif 后面,不能用在 #ifdef#ifndef 里。你想想看,#ifdef 本身就是 #if defined(...) 的语法糖,没必要再套一层。

7.3 复杂条件表达式

条件编译支持 &&||! 这些逻辑运算符,也支持 ==!=<> 等比较运算符。甚至可以用括号改变优先级:

#define FEATURE_A 1
#define FEATURE_B 0
#define PLATFORM_ID 2

#if (defined(FEATURE_A) && FEATURE_A == 1) || \
    (defined(PLATFORM_ID) && PLATFORM_ID >= 2)
    // 启用高级功能
    void advanced_init(void);
#endif

注意反斜杠 \ 是用来换行的。预处理指令本身是一行,太长的话可以用反斜杠续行。我曾经见过有人把十几个条件写在一行里,那代码简直没法看……

警告:条件表达式里的标识符,如果未被定义,会被替换成 0。所以 #if UNDEFINED_MACRO 等价于 #if 0,这个分支会被跳过。这既是特性也是陷阱——拼写错误可能导致静默跳过代码。

7.4 实战:跨平台代码管理

好了,理论说完了,咱们来点实际的。跨平台代码管理是 #elifdefined() 最经典的应用场景。我在做嵌入式项目时,经常需要一套代码同时支持 STM32、ESP32 和 Linux 模拟器。

下面是一个简化版的跨平台内存管理模块:

// mem_manager.h
#ifndef MEM_MANAGER_H
#define MEM_MANAGER_H

#include <stddef.h>

// 平台检测
#if defined(STM32F4)
    #include "stm32f4xx_hal.h"
    #define HEAP_SIZE  (64 * 1024)   // 64KB
    #define ALIGNMENT  8
#elif defined(ESP32)
    #include "esp_heap_caps.h"
    #define HEAP_SIZE  (256 * 1024)  // 256KB
    #define ALIGNMENT  4
#elif defined(__linux__)
    #include <stdlib.h>
    #define HEAP_SIZE  (1024 * 1024) // 1MB
    #define ALIGNMENT  16
#else
    #error "Unsupported platform! Define STM32F4, ESP32, or __linux__"
#endif

// 内存分配函数
void* my_malloc(size_t size);
void my_free(void* ptr);

#endif // MEM_MANAGER_H

对应的实现文件:

// mem_manager.c
#include "mem_manager.h"

static char heap[HEAP_SIZE];
static size_t heap_index = 0;

void* my_malloc(size_t size) {
    // 对齐处理
    size = (size + ALIGNMENT - 1) & ~(ALIGNMENT - 1);
    
    if (heap_index + size > HEAP_SIZE) {
        return NULL;
    }
    
    void* ptr = &heap[heap_index];
    heap_index += size;
    
#if defined(STM32F4)
    // STM32 需要清空缓存
    SCB_CleanInvalidateDCache();
#elif defined(ESP32)
    // ESP32 需要确保内存在 DMA 区域
    if ((uint32_t)ptr & 0x3) {
        // 地址未对齐,记录警告
    }
#endif
    
    return ptr;
}

void my_free(void* ptr) {
    // 简化实现:不支持释放
    (void)ptr;
}

你看,通过 #if-#elif-#else,我们把平台相关的代码隔离得干干净净。添加新平台时,只需要在头文件里加一个分支,其他地方基本不用动。

7.5 知识体系图

下面这张图总结了本章的核心逻辑:

多分支条件编译核心逻辑 #if / #ifdef / #ifndef 条件成立? 成立 不成立 保留该分支代码 跳过后续 #elif / #else 检查 #elif 或进入 #else #endif 结束 支持 defined()、&&、||、比较运算符等复杂表达式

7.6 避坑指南

最后分享几个我踩过的坑:

  • 宏定义拼写错误:我曾经把 STM32F4 写成 STM32F4(少了个下划线),结果编译器默默走了 #else 分支,导致整个项目链接失败。排查了整整一下午……
  • 忘记 #endif:每个 #if 都必须有对应的 #endif。嵌套使用时尤其容易漏掉。我习惯在 #endif 后面加注释:#endif /* PLATFORM_CHECK */
  • defined() 与 #ifdef 混用#ifdef MACRO 等价于 #if defined(MACRO),但 #ifdef 不支持 &&||。需要组合条件时,老老实实用 #if defined()
  • 条件表达式中的宏展开#if VALUE == 2 要求 VALUE 在预处理阶段就能展开成整数常量。如果 VALUEsizeof(int) 这种运行时值,编译器会报错。
核心要点:多分支条件编译是跨平台开发的基石。用好 #elifdefined(),能让你的代码在多个平台间优雅切换。记住——条件编译不是用来隐藏代码的,而是用来管理差异的。

公众号:蓝海资料掘金营,微信deep3321