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
", val); }
else { printf("CONFIG_ITEM_##b (##a): Failed to read
"); }
}
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.
*/