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

时间:2008-11-05 03:25:06

标签: c include c-preprocessor

X-Macros中提供了基本定义和示例以及“wikipedia entry on the C pre-processor”的一些参考:

  

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

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

3 个答案:

答案 0 :(得分:21)

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

X宏()的最常见用途是将错误文本与错误代码相关联。添加新的错误代码时,程序员必须记住添加代码和文本,通常是在不同的地方。 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;
  }

答案 1 :(得分:10)

几年前,当我开始在我的代码中使用函数指针时,我发现了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-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

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

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

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

答案 2 :(得分:5)

博士。 Dobb在这方面有一个article