Looking for a good explanation of the table generation macro idiom

后端 未结 1 414
傲寒
傲寒 2020-12-18 00:58

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

相关标签:
1条回答
  • 2020-12-18 01:43

    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.
     */
    
    0 讨论(0)
提交回复
热议问题