代码之家  ›  专栏  ›  技术社区  ›  detly FBruynbroeck

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

  •  6
  • detly FBruynbroeck  · 技术社区  · 14 年前

    我想提前澄清一下:我知道这个诀窍是如何运作的,我想要的是一个与其他人分享的清晰解释的链接。

    答案之一 C macro question 讨论“x macro”或“尚未定义的macro”习语。这包括定义如下内容:

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

    您可以用不同的 #define 对于 X() 创建一个值数组,可能调试字符串等。

    我想链接到一个明确的解释,这是如何工作,针对某人谁是相当熟悉 C . 不过,我不知道大家通常怎么称呼这个模式,所以到目前为止,我在网上搜索这个模式的尝试都失败了。

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

    2 回复  |  直到 8 年前
        1
  •  4
  •   Matt Curtis    14 年前

    我第一次在Dr Dobbs Journal(还是C用户日志)中了解到X宏?在兰迪·迈尔斯的一篇关于C99的文章中。

    尽管杂志不见了,但文章是 online here .

        2
  •  6
  •   Vicky    14 年前

    关于C预处理器的维基百科页面提到了这一点,但IMO并不清楚: 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.
     */