什么是在C(或可能是C ++)中记录X-Macros使用模式的好参考?

C预处理器的维基百科条目中给出了“ X-Macros ”的基本定义和示例以及一些参考:

X-Macro是一个头文件(通常使用“.def”扩展名而不是传统的“.h”),它包含一个类似的宏调用列表(可以称为“组件宏”)。

关于如何使用这种强大技术的一些很好的信息来源是什么? 是否有使用此方法的着名开源库?

我在代码中经常使用X Macros()。 该值来自仅将新数据添加到“X列表”而不修改任何其他代码。

X宏()的最常见用途是将错误文本与错误代码相关联。 添加新的错误代码时,程序员必须记住添加代码和文本,通常在不同的位置。 X宏允许将新的错误数据添加到一个位置,并在需要的任何地方自动填充。

不幸的是,这些机制使用了许多预编译器魔法,这些魔法会使代码难以阅读(例如,使用token1##token2字符串连接,使用token1##token2创建字符串)。 因此,我通常会在注释中解释X宏正在做什么。

以下是使用错误/返回值的示例。 所有新数据都会添加到“ X_ERROR ”列表中。 其他代码都没有被修改。

 /* * X Macro() data list * Format: Enum, Value, Text */ #define X_ERROR \ X(ERROR_NONE, 1, "Success") \ X(ERROR_SYNTAX, 5, "Invalid syntax") \ X(ERROR_RANGE, 8, "Out of range") /* * Build an array of error return values * eg {0,5,8} */ static int ErrorVal[] = { #define X(Enum,Val,Text) Val, X_ERROR #undef X }; /* * Build an array of error enum names * eg {"ERROR_NONE","ERROR_SYNTAX","ERROR_RANGE"} */ static char * ErrorEnum[] = { #define X(Enum,Val,Text) #Enum, X_ERROR #undef X }; /* * Build an array of error strings * eg {"Success","Invalid syntax","Out of range"} */ static char * ErrorText[] = { #define X(Enum,Val,Text) Text, X_ERROR #undef X }; /* * Create an enumerated list of error indexes * eg 0,1,2 */ enum { #define X(Enum,Val,Text) IDX_##Enum, X_ERROR #undef X IDX_MAX /* Array size */ }; void showErrorInfo(void) { int i; /* * Access the values */ for (i=0; i 

您还可以使用X Macros()生成代码。 例如,要测试错误值是否为“已知”,X宏可以在switch语句中生成案例:

  /* * Test validity of an error value * case ERROR_SUCCESS: * case ERROR_SYNTAX: * case ERROR_RANGE: */ switch(value) { #define X(Enum,Val,Text) case Val: X_ERROR #undef X printf("Error %d is ok\n",value); break; default: printf("Invalid error: %d\n",value); break; } 

几年前,当我开始在代码中使用函数指针时,我发现了X-macro。 我是一名嵌入式程序员,经常使用状态机。 我经常写这样的代码:

 /* declare an enumeration of state codes */ enum{ STATE0, STATE1, STATE2, ... , STATEX, NUM_STATES}; /* declare a table of function pointers */ p_func_t jumptable[NUM_STATES] = {func0, func1, func2, ... , funcX}; 

问题是我认为它非常容易出错,必须维护我的函数指针表的顺序,以便它匹配我的枚举状态的顺序。

我的一个朋友向我介绍了X-macros,就像一个灯泡在我脑海中消失了。 说真的,你在哪里生活过x-macros!

所以现在我定义下表:

 #define STATE_TABLE \ ENTRY(STATE0, func0) \ ENTRY(STATE1, func1) \ ENTRY(STATE2, func2) \ ... ENTRY(STATEX, funcX) \ 

我可以使用它如下:

 enum { #define ENTRY(a,b) a, STATE_TABLE #undef ENTRY NUM_STATES }; 

 p_func_t jumptable[NUM_STATES] = { #define ENTRY(a,b) b, STATE_TABLE #undef ENTRY }; 

作为奖励,我也可以让预处理器构建我的函数原型如下:

 #define ENTRY(a,b) static void b(void); STATE_TABLE #undef ENTRY 

另一种用法是声明和初始化寄存器

 #define IO_ADDRESS_OFFSET (0x8000) #define REGISTER_TABLE\ ENTRY(reg0, IO_ADDRESS_OFFSET + 0, 0x11)\ ENTRY(reg1, IO_ADDRESS_OFFSET + 1, 0x55)\ ENTRY(reg2, IO_ADDRESS_OFFSET + 2, 0x1b)\ ... ENTRY(regX, IO_ADDRESS_OFFSET + X, 0x33)\ /* declare the registers (where _at_ is a compiler specific directive) */ #define ENTRY(a, b, c) volatile uint8_t a _at_ b: REGISTER_TABLE #undef ENTRY /* initialize registers */ #def ENTRY(a, b, c) a = c; REGISTER_TABLE #undef ENTRY 

然而,我最喜欢的用途是通信处理程序

首先,我创建一个包含每个命令名称和代码的comms表:

 #define COMMAND_TABLE \ ENTRY(RESERVED, reserved, 0x00) \ ENTRY(COMMAND1, command1, 0x01) \ ENTRY(COMMAND2, command2, 0x02) \ ... ENTRY(COMMANDX, commandX, 0x0X) \ 

我在表中有大写和小写的名称,因为大写将用于枚举,小写用于函数名称。

然后我还为每个命令定义了结构,以定义每个命令的外观:

 typedef struct {...}command1_cmd_t; typedef struct {...}command2_cmd_t; etc. 

同样,我为每个命令响应定义结构:

 typedef struct {...}response1_resp_t; typedef struct {...}response2_resp_t; etc. 

然后我可以定义我的命令代码枚举:

 enum { #define ENTRY(a,b,c) a##_CMD = c, COMMAND_TABLE #undef ENTRY }; 

我可以定义我的命令长度枚举:

 enum { #define ENTRY(a,b,c) a##_CMD_LENGTH = sizeof(b##_cmd_t); COMMAND_TABLE #undef ENTRY }; 

我可以定义我的响应长度枚举:

 enum { #define ENTRY(a,b,c) a##_RESP_LENGTH = sizeof(b##_resp_t); COMMAND_TABLE #undef ENTRY }; 

我可以确定有多少命令如下:

 typedef struct { #define ENTRY(a,b,c) uint8_t b; COMMAND_TABLE #undef ENTRY } offset_struct_t; #define NUMBER_OF_COMMANDS sizeof(offset_struct_t) 

注意:我从来没有实际实例化offset_struct_t,我只是用它作为编译器为我生成我的命令数量的一种方式。

注意我可以生成我的函数指针表,如下所示:

 p_func_t jump_table[NUMBER_OF_COMMANDS] = { #define ENTRY(a,b,c) process_##b, COMMAND_TABLE #undef ENTRY } 

我的函数原型:

 #define ENTRY(a,b,c) void process_##b(void); COMMAND_TABLE #undef ENTRY 

最后,对于有史以来最酷的用途,我可以让编译器计算我的传输缓冲区应该有多大。

 /* reminder the sizeof a union is the size of its largest member */ typedef union { #define ENTRY(a,b,c) uint8_t b##_buf[sizeof(b##_cmd_t)]; COMMAND_TABLE #undef ENTRY }tx_buf_t 

同样,这个联合就像我的偏移结构,它没有实例化,而是我可以使用sizeof运算符来声明我的传输缓冲区大小。

 uint8_t tx_buf[sizeof(tx_buf_t)]; 

现在我的传输缓冲区tx_buf是最佳大小,当我向这个comms处理程序添加命令时,我的缓冲区将始终是最佳大小。 凉!

Dobb博士有一篇关于此的文章 。