寻找表生成宏惯用语的良好解释

发布于 2024-09-03 10:52:31 字数 705 浏览 2 评论 0原文

我想预先澄清这一点:我知道这个技巧是如何工作的,我想要的是一个清晰解释的链接以与其他人分享。

C 的答案之一宏问题谈论“X宏”或“尚未定义的宏”习语。这涉及到定义类似的内容:

#define MAGIC_LIST \
    X(name_1, default_1) \
    X(name_2, default_2) \
    ...

然后创建一个具有命名索引的值数组,您可以执行以下操作:

typedef enum {
#define X(name, val) name,

    MAGIC_LIST

#undef X
} NamedDefaults;

您可以使用不同的 #defineX() 重复该过程创建一个值数组,也许还可以调试字符串等。

我想要一个链接,清楚地解释它是如何工作的,适合那些对C还算熟悉的人。不过,我不知道每个人通常如何称呼这种模式,所以到目前为止,我在网络上搜索它的尝试都失败了。

(如果SO有这样的解释就好了……)

I want to make this clear up front : I know how this trick works, what I want is a link to a clear explanation to share with others.

One of the answers to a C macro question talks about the "X macro" or "not yet defined macro" idiom. This involves defining something like:

#define MAGIC_LIST \
    X(name_1, default_1) \
    X(name_2, default_2) \
    ...

Then to create, say, an array of values with named indices you do:

typedef enum {
#define X(name, val) name,

    MAGIC_LIST

#undef X
} NamedDefaults;

You can repeat the procedure with a different #define for X() to create an array of values, and maybe debugging strings, etc.

I'd like a link to a clear explanation of how this works, pitched at someone who is passably familiar with C. I have no idea what everyone usually calls this pattern, though, so my attempts to search the web for it have failed thus far.

(If there is such an explanation on SO, that'd be fine...)

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

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

发布评论

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

评论(1

时光与爱终年不遇 2024-09-10 10:52:31

关于 C 预处理器的维基百科页面提到了它,但在我看来并不是很清楚:
http://en.wikipedia.org/wiki/C_preprocessor#X-Macros

我为我的团队写了一篇关于它的论文;如果您愿意,请随意使用它。

/* X-macros are a way to use the C pre-processor to provide tuple-like 
 * functionality that would not otherwise be easy to implement in C. 
 * Any time you find yourself writing a comment that says something 
 * like "These values must be kept in sync with the values in typedef enum
 * foo_t", or adding a new item to a list and copying and pasting functions
 * to handle it, then X-macros are probably a better way to implement the 
 * behaviour you want.
 */


/* Begin with the main definition of the table of tuples. This can be directly 
 * in the header file, or in a separate #included template file. This example
 * is from some hardware revision reporting code.
 */


/*
 * Board versions
 * Upper bound resistor value, hardware version, hardware version string
 */
#define APP_HW_VERSIONS \
    X(0,  HW_UNKNOWN,    UNKNOWN_HW_VER) \
    X(8,  HW_NO_VERSION, "XDEV") /* Unversioned board (e.g. dev board) */ \
    X(24, HW_REVA,       "REVA") \
    X(39, HW_REVB,       "REVB") \
    X(54, HW_REVD,       "REVD") \
    X(71, HW_REVE,       "REVE") \
    X(88, HW_REVF,       "REVF") \
    X(103,HW_REVG,       "REVG") \
    X(118,HW_REVH,       "REVH") \
    X(137,HW_REVI,       "REVI") \
    X(154,HW_REVJ,       "REVJ") \
    /* add new versions above here */ \
    X(255,HW_REVX,       "REVX") /* Unknown newer version */


/* Now, any time you need to use the contents of this table, you redefine the
 * X(a,b,c) macro to give the behaviour you want. In the hardware revision
 * example, the first thing we need is an enumerated type giving the 
 * possible options for the value of the hardware revision. 
 */

#define X(a,b,c) b,
typedef enum {
APP_HW_VERSIONS
} app_hardware_version_t;
#undef X

/* The next thing we need in this example is some code to extract the 
 * hardware revision from the value of the version resistors.
 */
static app_hardware_version_t read_board_version(
    board_aio_id_t identifier,
    board_aio_val_t value
    )
{
    app_hardware_version_t app_hw_version;

    /* Determine board version based on ADC reading */
#define X(a,b,c) if (value < a) {app_hw_version = b;} else
APP_HW_VERSIONS
#undef X
    {
        app_hw_version = HW_UNKNOWN;
    }

    return app_hw_version;
}

/* Now we have two different places that need to extract the hardware revision 
 * as a string: the MMI info screen and the ATI command. 
 */

/* in the info screen code: */ 
    switch(ver)
    {
#define X(a,b,c) case b: ascii_to_display_string((lcd_char_t *) &app[0], c, HW_VER_STRING_LEN); break;
    APP_HW_VERSIONS
#undef X
    default:
        ascii_to_display_string((lcd_char_t *) &app[0], UNKNOWN_HW_VER, HW_VER_STRING_LEN);
        break;
    }

/* in the ATI handling code: */
    switch(ver)
    {
#define X(a,b,c) case b: strncpy(&p_data, (const uint8_t *) c, HW_VER_STRING_LEN); break;
    APP_HW_VERSIONS
#undef X

    default: 
        strncpy_write(&p_data, (const uint8_t *) UNKNOWN_HW_VER, HW_VER_STRING_LEN); 
        break;
    }

/* Another common example use case is auto-generation of accessor and mutator 
 * functions for a list of storage keys
 */

 /* First the tuple table */

 /* Configuration items: 
  * Storage key ID, name, type, min value, max value
  */
#define CONFIG_ITEMS \
    X(1234, DEVICE_ID, uint16_t, 0, 0xFFFF) \
    X(1235, NUM_CONNECTIONS, uint8_t, 0, 8) \
    X(1236, ENABLE_LOGGING, bool_t, 0, 1) \
    X(1237, SECURITY_KEY, uint32_t, 0, 0xFFFFFFFF)
    /* add new items above here */

/* Generate the enumerated type of keys */    
#define X(a,b,c,d,e) CONFIG_ITEM_##b = a,
typedef enum {
    CONFIG_ITEMS
    } config_item_t;
#undef X

 /* Generate the accessor functions */
#define X(a,b,c,d,e) \
    int get_config_item_##b(void *p_buf) \
    { \
        return read_from_key(a, sizeof(c), p_buf); \
    }  
CONFIG_ITEMS
#undef X

/* Generate the mutator functions */
#define X(a,b,c,d,e) \
    bool_t set_config_item_##b(void *p_buf) \
    { \
        c val = * (c*) p_buf; \
        if (val < d || val > e) return FALSE; \
        return write_to_key(a, sizeof(c), p_buf); \
    }
CONFIG_ITEMS
#undef X

/* Or, if you prefer, one big generic accessor function */
int get_config_item(config_item_t id, void *p_buf)
{
    switch (id)
    {
#define X(a,b,c,d,e) case a: return read_from_key(a, sizeof(c), p_buf); break;
    CONFIG_ITEMS
#undef X
    default:
        return 0;
    }
}

/* and one big generic mutator function */
bool_t set_config_item(config_item_t id, void *p_buf)
{
    switch (id)
    {
#define X(a,b,c,d,e) \
    case a: \
        { \
            c val = * (c*) p_buf; \
            if (val < d || val > e) return FALSE; \
            return write_to_key(a, sizeof(c), p_buf); \
        }

    CONFIG_ITEMS       
#undef X

    default:
        return FALSE;
    }
}

/* Finally let's add a logging function to dump all the config items */
void log_config_items(void)
{
#define X(a,b,c,d,e) \
    { \
        c val; \
        if (read_from_key(a, sizeof(c), &val) == sizeof(c)) \
        { printf("CONFIG_ITEM_##b (##a): 0x%x\n", val); } \
        else { printf("CONFIG_ITEM_##b (##a): Failed to read\n"); } \
    }
    CONFIG_ITEMS
#undef X
}


/* Now, when you need to add a new item to your list of config keys, you don't
 * need to update the enumerated type and copy and paste new get and set 
 * functions for each new key; you simply update the table of tuples and the
 * pre-processor takes care of the rest.
 */

The Wikipedia page about the C preprocessor mentions it but is not brilliantly clear IMO:
http://en.wikipedia.org/wiki/C_preprocessor#X-Macros

I wrote a paper about it for my group; feel free to use this if you wish.

/* X-macros are a way to use the C pre-processor to provide tuple-like 
 * functionality that would not otherwise be easy to implement in C. 
 * Any time you find yourself writing a comment that says something 
 * like "These values must be kept in sync with the values in typedef enum
 * foo_t", or adding a new item to a list and copying and pasting functions
 * to handle it, then X-macros are probably a better way to implement the 
 * behaviour you want.
 */


/* Begin with the main definition of the table of tuples. This can be directly 
 * in the header file, or in a separate #included template file. This example
 * is from some hardware revision reporting code.
 */


/*
 * Board versions
 * Upper bound resistor value, hardware version, hardware version string
 */
#define APP_HW_VERSIONS \
    X(0,  HW_UNKNOWN,    UNKNOWN_HW_VER) \
    X(8,  HW_NO_VERSION, "XDEV") /* Unversioned board (e.g. dev board) */ \
    X(24, HW_REVA,       "REVA") \
    X(39, HW_REVB,       "REVB") \
    X(54, HW_REVD,       "REVD") \
    X(71, HW_REVE,       "REVE") \
    X(88, HW_REVF,       "REVF") \
    X(103,HW_REVG,       "REVG") \
    X(118,HW_REVH,       "REVH") \
    X(137,HW_REVI,       "REVI") \
    X(154,HW_REVJ,       "REVJ") \
    /* add new versions above here */ \
    X(255,HW_REVX,       "REVX") /* Unknown newer version */


/* Now, any time you need to use the contents of this table, you redefine the
 * X(a,b,c) macro to give the behaviour you want. In the hardware revision
 * example, the first thing we need is an enumerated type giving the 
 * possible options for the value of the hardware revision. 
 */

#define X(a,b,c) b,
typedef enum {
APP_HW_VERSIONS
} app_hardware_version_t;
#undef X

/* The next thing we need in this example is some code to extract the 
 * hardware revision from the value of the version resistors.
 */
static app_hardware_version_t read_board_version(
    board_aio_id_t identifier,
    board_aio_val_t value
    )
{
    app_hardware_version_t app_hw_version;

    /* Determine board version based on ADC reading */
#define X(a,b,c) if (value < a) {app_hw_version = b;} else
APP_HW_VERSIONS
#undef X
    {
        app_hw_version = HW_UNKNOWN;
    }

    return app_hw_version;
}

/* Now we have two different places that need to extract the hardware revision 
 * as a string: the MMI info screen and the ATI command. 
 */

/* in the info screen code: */ 
    switch(ver)
    {
#define X(a,b,c) case b: ascii_to_display_string((lcd_char_t *) &app[0], c, HW_VER_STRING_LEN); break;
    APP_HW_VERSIONS
#undef X
    default:
        ascii_to_display_string((lcd_char_t *) &app[0], UNKNOWN_HW_VER, HW_VER_STRING_LEN);
        break;
    }

/* in the ATI handling code: */
    switch(ver)
    {
#define X(a,b,c) case b: strncpy(&p_data, (const uint8_t *) c, HW_VER_STRING_LEN); break;
    APP_HW_VERSIONS
#undef X

    default: 
        strncpy_write(&p_data, (const uint8_t *) UNKNOWN_HW_VER, HW_VER_STRING_LEN); 
        break;
    }

/* Another common example use case is auto-generation of accessor and mutator 
 * functions for a list of storage keys
 */

 /* First the tuple table */

 /* Configuration items: 
  * Storage key ID, name, type, min value, max value
  */
#define CONFIG_ITEMS \
    X(1234, DEVICE_ID, uint16_t, 0, 0xFFFF) \
    X(1235, NUM_CONNECTIONS, uint8_t, 0, 8) \
    X(1236, ENABLE_LOGGING, bool_t, 0, 1) \
    X(1237, SECURITY_KEY, uint32_t, 0, 0xFFFFFFFF)
    /* add new items above here */

/* Generate the enumerated type of keys */    
#define X(a,b,c,d,e) CONFIG_ITEM_##b = a,
typedef enum {
    CONFIG_ITEMS
    } config_item_t;
#undef X

 /* Generate the accessor functions */
#define X(a,b,c,d,e) \
    int get_config_item_##b(void *p_buf) \
    { \
        return read_from_key(a, sizeof(c), p_buf); \
    }  
CONFIG_ITEMS
#undef X

/* Generate the mutator functions */
#define X(a,b,c,d,e) \
    bool_t set_config_item_##b(void *p_buf) \
    { \
        c val = * (c*) p_buf; \
        if (val < d || val > e) return FALSE; \
        return write_to_key(a, sizeof(c), p_buf); \
    }
CONFIG_ITEMS
#undef X

/* Or, if you prefer, one big generic accessor function */
int get_config_item(config_item_t id, void *p_buf)
{
    switch (id)
    {
#define X(a,b,c,d,e) case a: return read_from_key(a, sizeof(c), p_buf); break;
    CONFIG_ITEMS
#undef X
    default:
        return 0;
    }
}

/* and one big generic mutator function */
bool_t set_config_item(config_item_t id, void *p_buf)
{
    switch (id)
    {
#define X(a,b,c,d,e) \
    case a: \
        { \
            c val = * (c*) p_buf; \
            if (val < d || val > e) return FALSE; \
            return write_to_key(a, sizeof(c), p_buf); \
        }

    CONFIG_ITEMS       
#undef X

    default:
        return FALSE;
    }
}

/* Finally let's add a logging function to dump all the config items */
void log_config_items(void)
{
#define X(a,b,c,d,e) \
    { \
        c val; \
        if (read_from_key(a, sizeof(c), &val) == sizeof(c)) \
        { printf("CONFIG_ITEM_##b (##a): 0x%x\n", val); } \
        else { printf("CONFIG_ITEM_##b (##a): Failed to read\n"); } \
    }
    CONFIG_ITEMS
#undef X
}


/* Now, when you need to add a new item to your list of config keys, you don't
 * need to update the enumerated type and copy and paste new get and set 
 * functions for each new key; you simply update the table of tuples and the
 * pre-processor takes care of the rest.
 */
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文