问题描述
我刚刚了解到 X-Macros.你见过 X-Macros 在现实世界中的哪些用途?它们什么时候适合这项工作?
I just learned of X-Macros. What real-world uses of X-Macros have you seen? When are they the right tool for the job?
推荐答案
几年前,当我开始在代码中使用函数指针时,我发现了 X 宏.我是一名嵌入式程序员,经常使用状态机.我经常会写这样的代码:
I discovered X-macros a couple of years ago when I started making use of function pointers in my code. I am an embedded programmer and I use state machines frequently. Often I would write code like this:
/* 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};
问题是我认为必须维护我的函数指针表的顺序以使其与我的状态枚举的顺序匹配非常容易出错.
The problem was that I considered it very error prone to have to maintain the ordering of my function pointer table such that it matched the ordering of my enumeration of states.
我的一个朋友向我介绍了 X-macros,这就像一个灯泡在我脑海中熄灭了.说真的,我这辈子你都去哪儿了 x-macros!
A friend of mine introduced me to X-macros and it was like a light-bulb went off in my head. Seriously, where have you been all my life x-macros!
所以现在我定义下表:
#define STATE_TABLE
ENTRY(STATE0, func0)
ENTRY(STATE1, func1)
ENTRY(STATE2, func2)
...
ENTRY(STATEX, funcX)
我可以按如下方式使用它:
And I can use it as follows:
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
};
作为奖励,我还可以让预处理器构建我的函数原型,如下所示:
as a bonus, I can also have the pre-processor build my function prototypes as follows:
#define ENTRY(a,b) static void b(void);
STATE_TABLE
#undef ENTRY
另一种用法是声明和初始化寄存器
Another usage is to declare and initialize registers
#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 */
#define ENTRY(a, b, c) a = c;
REGISTER_TABLE
#undef ENTRY
然而,我最喜欢的用法是在通信处理程序方面
My favourite usage however is when it comes to communication handlers
首先我创建一个通信表,包含每个命令名称和代码:
First I create a comms table, containing each command name and code:
#define COMMAND_TABLE
ENTRY(RESERVED, reserved, 0x00)
ENTRY(COMMAND1, command1, 0x01)
ENTRY(COMMAND2, command2, 0x02)
...
ENTRY(COMMANDX, commandX, 0x0X)
我在表中有大写和小写名称,因为大写将用于枚举,小写用于函数名称.
I have both the uppercase and lowercase names in the table, because the upper case will be used for enums and the lowercase for function names.
然后我还为每个命令定义结构体来定义每个命令的样子:
Then I also define structs for each command to define what each command looks like:
typedef struct {...}command1_cmd_t;
typedef struct {...}command2_cmd_t;
etc.
同样,我为每个命令响应定义了结构:
Likewise I define structs for each command response:
typedef struct {...}command1_resp_t;
typedef struct {...}command2_resp_t;
etc.
然后我可以定义我的命令代码枚举:
Then I can define my command code enumeration:
enum
{
#define ENTRY(a,b,c) a##_CMD = c,
COMMAND_TABLE
#undef ENTRY
};
我可以定义我的命令长度枚举:
I can define my command length enumeration:
enum
{
#define ENTRY(a,b,c) a##_CMD_LENGTH = sizeof(b##_cmd_t);
COMMAND_TABLE
#undef ENTRY
};
我可以定义我的响应长度枚举:
I can define my response length enumeration:
enum
{
#define ENTRY(a,b,c) a##_RESP_LENGTH = sizeof(b##_resp_t);
COMMAND_TABLE
#undef ENTRY
};
我可以确定有多少个命令如下:
I can determine how many commands there are as follows:
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,我只是将它用作编译器为我生成我的命令定义数量的一种方式.
NOTE: I never actually instantiate the offset_struct_t, I just use it as a way for the compiler to generate for me my number of commands definition.
请注意,我可以按如下方式生成函数指针表:
Note then I can generate my table of function pointers as follows:
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
现在终于有史以来最酷的使用了,我可以让编译器计算我的传输缓冲区应该有多大.
Now lastly for the coolest use ever, I can have the compiler calculate how big my transmit buffer should be.
/* 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 运算符来声明我的传输缓冲区大小.
Again this union is like my offset struct, it is not instantiated, instead I can use the sizeof operator to declare my transmit buffer size.
uint8_t tx_buf[sizeof(tx_buf_t)];
现在我的传输缓冲区 tx_buf 是最佳大小,当我向此通信处理程序添加命令时,我的缓冲区将始终是最佳大小.酷!
Now my transmit buffer tx_buf is the optimal size and as I add commands to this comms handler, my buffer will always be the optimal size. Cool!
另一个用途是创建偏移表:由于内存通常是嵌入式系统的一个限制,当它是一个稀疏数组时,我不想为我的跳转表使用 512 个字节(每个指针 2 个字节 X 256 个可能的命令).相反,我将为每个可能的命令创建一个 8 位偏移表.这个偏移量然后用于索引到我的实际跳转表,现在只需要 NUM_COMMANDS * sizeof(pointer).在我的例子中定义了 10 个命令.我的跳转表是 20 字节长,我有一个 256 字节长的偏移表,总共是 276 字节而不是 512 字节.然后我像这样调用我的函数:
One other use is to create offset tables:Since memory is often a constraint on embedded systems, I don't want to use 512 bytes for my jump table (2 bytes per pointer X 256 possible commands) when it is a sparse array. Instead I will have a table of 8bit offsets for each possible command. This offset is then used to index into my actual jump table which now only needs to be NUM_COMMANDS * sizeof(pointer). In my case with 10 commands defined. My jump table is 20bytes long and I have an offset table that is 256 bytes long, which is a total of 276bytes instead of 512bytes. I then call my functions like so:
jump_table[offset_table[command]]();
代替
jump_table[command]();
我可以像这样创建一个偏移表:
I can create an offset table like so:
/* initialize every offset to 0 */
static uint8_t offset_table[256] = {0};
/* for each valid command, initialize the corresponding offset */
#define ENTRY(a,b,c) offset_table[c] = offsetof(offset_struct_t, b);
COMMAND_TABLE
#undef ENTRY
其中 offsetof 是在stddef.h"中定义的标准库宏
where offsetof is a standard library macro defined in "stddef.h"
作为附带好处,有一种非常简单的方法可以确定是否支持命令代码:
As a side benefit, there is a very easy way to determine if a command code is supported or not:
bool command_is_valid(uint8_t command)
{
/* return false if not valid, or true (non 0) if valid */
return offset_table[command];
}
这也是为什么在我的 COMMAND_TABLE 中我保留了命令字节 0.我可以创建一个名为process_reserved()"的函数,如果任何无效的命令字节被用于索引到我的偏移表中,它将被调用.
This is also why in my COMMAND_TABLE I reserved command byte 0. I can create one function called "process_reserved()" which will be called if any invalid command byte is used to index into my offset table.
这篇关于X-Macros 的实际使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!