20 宏的字符串化与记号拼接

一、核心结论

预处理运算符作用例子结果
#将宏参数字符串化RAW(BLE)"BLE"
##拼接预处理记号JOIN(key, 2)key2(标识符)

两者都发生在预处理阶段,不是运行时字符串操作。## 拼接结果必须构成合法的预处理记号。

二、#:将参数变成字符串

#include <stdio.h>
#define RAW(x) #x

int main(void)
{
    printf("%s\n", RAW(BLE)); // 输出 BLE
    return 0;
}
RAW(BLE) ── # 字符串化 ──> "BLE" ── printf("%s") ──> BLE
                         字符串字面量                打印内容不带引号

三、##:将两个记号拼成一个

#include <stdio.h>
#define JOIN(a, b) a##b

int main(void)
{
    int key2 = 20;
    printf("%d\n", JOIN(key, 2)); // 输出 20
    return 0;
}
JOIN(key, 2) ── ## 拼接 ──> key2 ── 读取变量值 ──> 20
                          变量名                整数值

注意: key2 是变量名,不是字符串 "key2"。

四、为什么单层与两层宏结果不同?

#include <stdio.h>

#define VERSION 456
#define RAW(x)  #x
#define TEXT(x) RAW(x)

int main(void)
{
    printf("%s %s\n", RAW(VERSION), TEXT(VERSION));
    return 0;
}

输出:

VERSION 456

展开过程图

单层:RAW(VERSION)
        │ 参数直接用于 #,不先展开 VERSION
        ▼
     "VERSION"

两层:TEXT(VERSION)
        │ 外层没有直接用 #,参数先展开
        ▼
      RAW(456)
        │ 内层再用 # 字符串化
        ▼
       "456"
写法预处理结果用途
RAW(VERSION)"VERSION"需要参数原本的写法
TEXT(VERSION)"456"需要宏展开后的内容

注意: 这里的“两层宏先展开”适用于上述常见字符串化写法;实际复杂宏还需要遵守预处理器的参数展开与重新扫描规则。

五、蓝牙/TWS 固件中的简单用途

#define FW_VERSION 123
#define RAW(x) #x
#define TEXT(x) RAW(x)

// 将编译期固件版本宏变成日志可打印的字符串:
const char *version_text = TEXT(FW_VERSION); // "123"

与位掩码、结构体布局等不同,这里的结果是在预处理阶段生成的,不需要运行时转换。

六、快速记忆

  1. # = 变字符串:RAW(BLE) → "BLE"。
  2. ## = 拼记号:JOIN(key, 2) → 变量名 key2。
  3. 直接 # 不先展开宏参数:RAW(VERSION) → "VERSION"。
  4. 外层宏先展开、内层再字符串化:TEXT(VERSION) → "456"。
  5. printf("%s", "VERSION") 输出 VERSION,不带引号。

口诀:井号变字符串,双井号拼名字;一层保留写法,两层先展开。

本篇不重复第 12 篇的重复求值、do...while(0)、inline 等内容。