Table of Contents
C X macros
C X macros are a metaprogramming technique where a macro is defined to represent a list, then included multiple times with different expansions to generate repetitive code. The same list definition generates enums, string arrays, case statements, and parser tables without manual duplication.
Use X macros to keep related data (enums, strings, handlers) synchronized and avoid repetition when the same list is used in multiple contexts.
Example
This example shows X macros generating enums, strings, and case statements from one list.
// compile: gcc -o xmacros xmacros.c // run: ./xmacros // description: X macro pattern for code generation #include <stdio.h> #include <string.h> // Define the list once (the "X" macro pattern) #define COLORS_LIST \ X(RED, "Red", 0xFF0000) \ X(GREEN, "Green", 0x00FF00) \ X(BLUE, "Blue", 0x0000FF) \ X(YELLOW, "Yellow", 0xFFFF00) \ X(CYAN, "Cyan", 0x00FFFF) // Generate enum #define X(name, str, hex) name, enum Color { COLORS_LIST COLOR_COUNT }; #undef X // Generate string array #define X(name, str, hex) str, const char *color_names[] = { COLORS_LIST }; #undef X // Generate RGB array #define X(name, str, hex) hex, const int color_values[] = { COLORS_LIST }; #undef X // Function to get color name const char *color_name(enum Color c) { if (c >= 0 && c < COLOR_COUNT) { return color_names[c]; } return "Unknown"; } // Function to get color value int color_value(enum Color c) { if (c >= 0 && c < COLOR_COUNT) { return color_values[c]; } return 0; } int main() { printf("Color enumeration:\n"); for (int i = 0; i < COLOR_COUNT; i++) { printf(" %d: %s (0x%06X)\n", i, color_name(i), color_value(i)); } // Use enum and look up values enum Color c = BLUE; printf("\nSelected color: %s = 0x%06X\n", color_name(c), color_value(c)); // Parse color by name const char *target = "GREEN"; for (int i = 0; i < COLOR_COUNT; i++) { if (strcmp(color_names[i], target) == 0) { printf("Found %s at index %d (0x%06X)\n", target, i, color_value(i)); break; } } return 0; }
Common patterns
Single definition, multiple expansions:
- Define macro
NAME_LISTonce withX(...)for each entry - Include multiple times with different
#define X(...) - Each inclusion expands to different output (enum, strings, handlers)
Synchronization benefit:
- Add/remove entry in one place
- All generated code updates automatically
- No manual enum/string/handler updates
- Eliminates sync bugs
Common use cases:
- Enums + string representations
- Enums + handler functions
- Enums + default values
- Enums + printf formatters
- Command handlers + dispatch tables
- Error codes + error messages
Dispatch table example:
#define HANDLERS_LIST \ X(CMD_START, handle_start) \ X(CMD_STOP, handle_stop) \ X(CMD_STATUS, handle_status) // Generate enum #define X(cmd, func) cmd, enum Command { HANDLERS_LIST }; #undef X // Generate handler table #define X(cmd, func) func, handler_t handlers[] = { HANDLERS_LIST }; #undef X
Variadic X usage:
X(name, ...)accepts multiple fields- Each expansion can use different subset
- Flexible for complex data
Debugging gotcha:
- Error messages show expanded code, not macro
- Use
gcc -Eto see expansion - Add comments in list for clarity
Alternatives:
- Python script to generate C code (external tool)
- Code generator (build step)
- Manual lists (prone to sync errors)
- X macros (pure C, no build tools)
Limitations:
- Macro syntax is cryptic
- Hard to debug (preprocessor output)
- Not type-safe (preprocessor is text-based)
- Can't use in many contexts where C++ templates work
Style considerations:
#undef Xafter each use to avoid pollution- Long list definitions can be hard to read
- Clear naming (
COLORS_LIST, notLIST) helps - Document the X macro pattern in comments
Modern alternatives:
- C++ templates (type-safe, readable)
- Code generation tools (clearer separation)
- Build scripts (flexible, external)
- But X macros remain pure-C solution
