C扩展4:X宏(X-MACRO)
代码从.c到可执行文件.elf/.exe会一次经历预编译、编译、汇编、链接四个阶段,而预编译过程中一个很重要的动作就是进行宏替换,所以可以利用这个阶段自动化地生成重复性的代码结构,特别是在处理协议字段、驱动注册、配置表等需要大量相似定义的场景。
思想是:通过宏和X-Macro模式,将一份“数据定义”同时用于生成枚举、结构体数组等多种数据结构,保证唯一数据源,减少重复和错误。
想象一个场景:你需要定义一组命令码或协议字段。通常你需要:
1.定义一个枚举(enum),给每个字段一个唯一的ID。
2.定义一个结构体数组,存放每个字段的处理函数和参数。
传统做法是写两份列表,很容易出现两个列表不同步的错误。而这组宏的终极目标就是:你只定义一份列表,宏自动帮你生成枚举和结构体数组。
#define DEF(a,b) a
int m = DEF(1,2);
#undef DEF
#define DEF(a,b) b
int n = DEF(1,2);
从宏的定义和取消来控制想要的输出,于是:
#define CMD_FUN \DEF(UP, up_func) \DEF(DOWN, down_func) \DEF(LEFT, left_func) \DEF(RIGHT, right_func) \
enum定义
typedef enum
{#define DEF(a,b) a,CMD_FUN#undef DEFCMD_END
};
在编译之后就变为
typedef enum
{UP, DOWN, LEFT, RIGHT, CMD_END
}MOVE_CMD;
同样地,也可以定义一个函数指针数组
const Move move_funcs[] =
{#define DEF(a,b) b,CMD_FUNC#undef DEF
};
//编译之后
const Move move_funcs[] =
{up_func,down_func,left_func,right_func
};
这样就通过预编译阶段做好了索引与回调函数的对应工作,在编译时会直接生成,后面就可以通过索引进行操作了,如
void function(MOVE_CMD mc)
{ if(mc>=CMD_END||p==NULL)return;move_funcs[mc]();
}
工程一般过程:
#define DISPATCH(fn, name, fun, param) fn(name, fun, param)
它接受四个参数,但本质上是将后三个参数(name, fun, param)传递给第一个参数(fn)所代表的宏或函数
#defineENUM_CONSTRUCT(name, fun, param) name,
当被DISPATCH调用时,fn就是这个宏,它会为列表中的每一项生成一个枚举值,后面加一个逗号,从而组合成一个完整的枚举定义
#define IOV_ITEM_STRUCT_CONSTRUCT(name, fun, _param) \
{ \
.update_fn = fun, \
.param = (int)(_param), \
},
专门用于初始化结构体数组元素的“片段”。
#define ENUM_DEF(type) \
typedef enum { MY##type(ENUM_CONSTRUCT) MY##type##_MAX } my##type##_e;
自动生成一个枚举类型
#define STRUCT_DEF(type, name) \
static item_t name##_items[] = { fun##type(STRUCT_CONSTRUCT) };
自动生成一个静态的结构体数组。