记录 C(或可能是 C++)中 X 宏的使用模式的良好参考资料是什么?

发布于 2024-07-08 13:32:11 字数 410 浏览 7 评论 0原文

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

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

关于如何使用这种强大技术的一些好的信息来源有哪些? 有知名的开源库使用这种方法吗?

A basic definition and example and a few references for "X-Macros" is given in this wikipedia entry on the C pre-processor:

An X-Macro is a header file (commonly
using a ".def" extension instead of
the traditional ".h") that contains a
list of similar macro calls (which can
be referred to as "component macros").

What are some good sources of information on how to use this powerful technique?
Are there well-known open source libraries using this method?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

飘逸的'云 2024-07-15 13:32:11

我在代码中经常使用 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;
  }

I use X Macros() in code a lot. The value comes from only adding new data only to the "X list" and not modifying any other code.

The most common use of X Macros() is for associating error text with error codes. When new error codes are added, programmers must remember to add the code and the text, typically in separate places. The X Macro allows the new error data to be added in a single place and get automatically populated anywhere it is needed.

Unfortunately, the mechanisms use a lot of pre-compiler magic that can make the code somewhat hard to read (e.g. string joining with token1##token2, string creation with #token). Because of this I typically explain what the X Macro is doing in the comments.

Here is an example using the error/return values. All new data gets added to the "X_ERROR" list. None of the other code hast to be modified.

/* 
 * 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]);

}

You can also use X Macros() to generate code. For example to test if an error value is "known", the X Macro can generate cases in a switch statement:

 /*
  * 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;
  }
舟遥客 2024-07-15 13:32:11

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

/* 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 宏,这就像我脑子里的一个灯泡突然亮了。 说真的,我这辈子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

然而,我最喜欢的用法是当涉及通信处理程序

首先,我创建一个通信表,其中包含每个命令名称和代码:

#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 是最佳大小,当我向此通信处理程序添加命令时,我的缓冲区将始终是最佳大小。 凉爽的!

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.

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!

So now I define the following table:

#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
};

and

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 */
#def 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 {...}response1_resp_t;
typedef struct {...}response2_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)

NOTE: I never actually instantiate the offset_struct_t, I just use it as a way for the compiler to generator for me my number of commands.

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
}

And my function prototypes:

#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

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)];

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!

半窗疏影 2024-07-15 13:32:11

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

Dr. Dobb's has an article on this.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文