Поиск хорошего объяснения идиомы макроса создания таблицы

Я хочу ясно дать понять это впереди: Я знаю, как этот прием работает, что я хочу, ссылка на четкое объяснение для совместного использования с другими.

Один из ответов на макро-вопрос 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;

Можно повторить процедуру с другим #define для X() создать массив значений и возможно отладку строк, и т.д.

Я хотел бы ссылку на четкое объяснение того, как это работает, переданное в ком-то, кто проходимо знаком с C. Я понятия не имею, что все обычно называют этим шаблоном, тем не менее, таким образом, мои попытки искать сеть его перестали работать к настоящему времени.

(Если бы существует такое объяснение на Так, это было бы прекрасно...),

6
задан Community 23 May 2017 в 12:07
поделиться

1 ответ

На странице Википедии о препроцессоре 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.
 */
6
ответ дан 17 December 2019 в 02:24
поделиться
Другие вопросы по тегам:

Похожие вопросы: