X-Macro (X宏)
X-Macro (X宏)我愿称为其为超级宏
是什么?
其将数据列表和命令分开,通过X宏你可以实现对同一组数据列表,执行不同命令的操作。其设计哲学类似于设备树,将描述数据与执行逻辑分开。
什么用?
#define FOREACH_STATE(S) S(STATE_INIT), S(STATE_RUNNING),S(STATE_PAUSED)……
- 这是一个宏套宏,第一层宏为FOREACH_STATE(S)=后面一大串S(STATE_INIT), S(STATE_RUNNING)……。详见以下代码。
- 第二层宏为, S可以被外界选择为各种各样的命令操作。例见第二步的展开策略。
- 其中的S可以被其它任意字符代替,其只是一个形式参数
S:代表 State(状态)或 Symbol。X:这是最经典的用法(也是 X-Macros 名称的由来)。ENTRY或ITEM:为了让代码读起来更像人话。
#include <stdio.h>
/*
* X-Macro 技术演示程序
*
* X-Macro 是一种 C 语言预处理器技术,通过宏定义来生成重复性代码
* 主要优势:
* 1. DRY (Don't Repeat Yourself) 原则 - 避免代码重复
* 2. 单一数据源 - 所有数据结构从同一源头生成
* 3. 自动同步 - 添加新状态时,所有相关结构自动更新
* 4. 减少维护成本 - 修改只需在一处进行
*/
/* 1. 定义状态列表(唯一真相源) */
//这是一个宏套宏,第一层为FOREACH_STATE(S)=后面一大串S(STATE_INIT), S(STATE_RUNNING)……
//第二层宏为, S可以被外界选择为各种各样的命令操作。例见第二步的展开策略。
#define FOREACH_STATE(S) \
S(STATE_INIT), /* 初始化状态 */ \
S(STATE_RUNNING), /* 运行状态 */ \
S(STATE_PAUSED), /* 暂停状态 */ \
S(STATE_STOPPED), /* 停止状态 */ \
S(STATE_FINISHED) /* 完成状态 */
/* 2. 定义"展开策略" - 不同的数据生成方式 */
#define GENERATE_ENUM(a) a /* 直接使用枚举名称 */
#define GENERATE_ENUM_add(a) a+1 /* 枚举值加1 */
#define GENERATE_STRING(s) #s /* 转换为字符串 */
#define GENERATE_STRING_end(s) #s"end" /* 转换为字符串并添加"end"后缀 */
/* 3. 用同一份状态表生成 enum */
enum my_state {
FOREACH_STATE(GENERATE_ENUM) /* 展开为: STATE_INIT, STATE_RUNNING, ... */
};
/* 4. 用同一份状态表生成字符串数组 */
static const char * const my_state_name[] = {
FOREACH_STATE(GENERATE_STRING) /* 展开为: "STATE_INIT", "STATE_RUNNING", ... */
};
/* 5. 用同一份状态表生成字符串数组,要求末尾加end */
static const char * const my_state_name_end[] = {
FOREACH_STATE(GENERATE_STRING_end) /* 展开为: "STATE_INITend", "STATE_RUNNINGend", ... */
};
/* 6. 用同一份状态表生成整数数组,要求每个值加1 */
static const int my_state_add[] = {
FOREACH_STATE(GENERATE_ENUM_add) /* 展开为: 1, 2, 3, 4, 5 */
};
int main(void)
{
enum my_state s;
printf("X-Macro 技术演示 - 多种数据结构自动生成\n");
printf("=============================================\n\n");
/*
* 遍历所有状态,展示从同一状态源生成的不同数据结构
* 注意:由于使用了 X-Macro,所有数组的大小和索引都是自动同步的
*/
for (s = STATE_INIT; s <= STATE_FINISHED; s++) {
/* 显示原始枚举值和对应的字符串名称 */
printf("状态值: %d, 名称: %s\n", s, my_state_name[s]);
/* 显示带"end"后缀的字符串 */
printf("状态值: %d, 后缀名称: %s\n", s, my_state_name_end[s]);
/* 显示加1后的整数和对应名称 */
printf("加1值: %d, 对应状态: %s\n", my_state_add[s], my_state_name[s]);
/* 显示加1后的整数和带后缀的名称 */
printf("加1值: %d, 后缀名称: %s\n", my_state_add[s], my_state_name_end[s]);
printf("------------------------------\n"); /* 分隔线,提高可读性 */
}
printf("\nX-Macro 技术优势总结:\n");
printf("1. 只需在 FOREACH_STATE 中添加新状态,所有数组自动扩展\n");
printf("2. 枚举值、字符串、派生数据保持同步\n");
printf("3. 减少手动维护错误\n");
printf("4. 代码更加简洁和一致\n");
return 0;
}
用在哪?
- 状态机 (State Machines)
如代码中的例子,在 USB-PD、蓝牙协议栈或 TCP/IP 协议中,状态非常多。
- 用途:同步定义状态枚举、状态转换日志(字符串)、以及状态处理函数的跳转表。
- 痛点:如果你手动维护,增加一个
STATE_RECOVERY状态时,很容易在switch-case里漏掉它,导致程序跑飞。
- 错误码与告警系统 (Error Codes)
在复杂的系统中,通常有几百个错误码。
- 用途:
- 生成枚举:
ERR_TIMEOUT,ERR_OUT_OF_MEMORY… - 生成描述:
"System timeout","Memory allocation failed"…
- 生成枚举:
- 优势:你可以一键生成一个 `const char* error_to_tring(int code) 函数,永远不会出错。
- 硬件寄存器与引脚配置 (Hardware IO/Registers)
在嵌入式开发(如 STM32 或 Linux 驱动)中,你需要配置大量的 GPIO 引脚。
- 用途:
#define GPIO_LIST(X) \ X(GPIOA, 5, "LED_RED") \ X(GPIOB, 12, "LCD_CS") \ X(GPIOC, 13, "BUTTON") - 优势:通过这份名单,你可以一次性生成:
- 引脚初始化的代码。
- 定义引脚索引的枚举。
- 调试时的引脚名称查询表。
- 命令行解析与菜单系统 (CLI / Shell)
如果你在做串口控制台(如同路由器或交换机的命令行)。
- 用途:
- 生成命令名列表。
- 生成对应的帮助信息(Help Message)。
- 生成对应的回调函数(Callback Functions)。
- 优势:增加一个新命令只需在列表里加一行,对应的帮助信息和函数关联就自动生成了。


1343

被折叠的 条评论
为什么被折叠?



