Modern C (C11/C17/C23)

an X-macro

/ EKS-MAK-roh /

Imagine you have a list of things — error codes, enum values, register names — and you need that same list in several forms: an enum, an array of name strings, a switch. Keeping those copies in sync by hand is exactly the kind of chore that drifts out of date. The X-macro is a preprocessor pattern that writes the list ONCE and generates each form from it.

You define the list as a macro that invokes a placeholder, conventionally X, once per item: #define COLORS(X) X(RED) X(GREEN) X(BLUE). Then, to generate one form, you define what X means, expand the list, and undefine X. For an enum you write #define X(name) name, then COLORS(X), giving RED, GREEN, BLUE inside an enum; for a name table you redefine #define X(name) #name, and the same COLORS(X) produces the strings "RED", "GREEN", "BLUE". The single source list drives every generated form, so adding a color in one place updates the enum and the table together.

It matters wherever parallel tables must stay consistent: opcode tables, protocol field lists, command dispatchers, and serialization code. The honest trade-offs: X-macros lean heavily on the preprocessor, so they can be hard to read and to debug (the expansion is invisible until you ask the compiler to dump it with a command like gcc -E), and modern alternatives include code generators or, where suitable, _Generic and designated initializers.

#define COLORS(X) X(RED) X(GREEN) X(BLUE) enum color { COLORS(GEN_ENUM) }; // with #define GEN_ENUM(n) n, static const char *names[] = { COLORS(GEN_STR) }; // #define GEN_STR(n) #n, // one list -> both the enum and the name table

The same COLORS list, expanded with two different X definitions, yields a matching enum and string table that never drift apart.

X-macros keep parallel tables in sync but are hard to read and debug; the generated text is invisible until you dump the preprocessor output (e.g. gcc -E).

Also called
X macro patterntable macro資料表巨集