在 C 预处理器的这个维基百科条目中给出了“ X-Macros ”的基本定义和示例以及一些参考:
X-Macro 是一个头文件(通常使用“.def”扩展名而不是传统的“.h”),其中包含类似宏调用的列表(可以称为“组件宏”)。
关于如何使用这种强大的技术,有哪些好的信息来源?有使用这种方法的知名开源库吗?
我在代码中经常使用 X Macros()。该值来自仅将新数据添加到“X 列表”而不修改任何其他代码。
X Macros() 最常见的用途是将错误文本与错误代码相关联。当添加新的错误代码时,程序员必须记住添加代码和文本,通常在不同的地方。X 宏允许在一个地方添加新的错误数据,并在任何需要的地方自动填充。
不幸的是,这些机制使用了很多预编译器的魔法,这会使代码有些难以阅读(例如,使用 连接token1##token2
字符串,使用 创建字符串#token
)。因此,我通常会在评论中解释 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
* e.g. {0,5,8}
*/
static int ErrorVal[] =
{
#define X(Enum,Val,Text) Val,
X_ERROR
#undef X
};
/*
* Build an array of error enum names
* e.g. {"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
* e.g. {"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
* e.g. 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<IDX_MAX; i++)
printf(" %s == %d [%s]\n", ErrorEnum[i], ErrorVal[i], ErrorText[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-macros。我是一名嵌入式程序员,经常使用状态机。我经常会写这样的代码:
/* 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 宏你都去哪儿了!
所以现在我定义下表:
#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 是最佳大小,当我向这个通信处理程序添加命令时,我的缓冲区将始终是最佳大小。凉爽的!
Dobb 博士对此有一篇文章。