Advanced Macros
Master advanced macro techniques: X-macros, stringification, token pasting, variadic macros, macro recursion, code generation, and metaprogramming patterns for powerful compile-time code manipulation.
Understanding Advanced Macros
Advanced macros enable powerful metaprogramming: generating repetitive code, creating DSLs, compile-time computations. Use carefully - macros lack type safety and debugging is harder. But when used correctly, they eliminate boilerplate and enable techniques impossible with regular C code.
#include <stdio.h>
/* Stringification (#) - convert to string */
#define STRINGIFY(x) #x
#define TO_STRING(x) STRINGIFY(x)
printf("%s\n", STRINGIFY(hello)); /* "hello" */
printf("%s\n", TO_STRING(100)); /* "100" */
/* Token pasting (##) - concatenate tokens */
#define CONCAT(a, b) a##b
int CONCAT(var, 123) = 42; /* int var123 = 42; */
/* Combined stringification and pasting */
#define DEFINE_GETTER(type, name) \
type get_##name(void) { \
return global_##name; \
}
int global_count = 10;
DEFINE_GETTER(int, count) /* Creates: int get_count(void) */
int value = get_count(); /* 10 */
/* Variadic macros (__VA_ARGS__) */
#define LOG(level, format, ...) \
printf("[%s] " format "\n", level, __VA_ARGS__)
LOG("INFO", "Value: %d", 42);
/* Expands to: printf("[%s] " "Value: %d" "\n", "INFO", 42); */
/* GNU extension: ##__VA_ARGS__ (handles empty args) */
#define LOG_GNU(format, ...) \
printf(format "\n", ##__VA_ARGS__)
LOG_GNU("No arguments"); /* Works! */
/* Counting arguments (complex) */
#define COUNT_ARGS(...) COUNT_ARGS_(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define COUNT_ARGS_(a1,a2,a3,a4,a5,a6,a7,a8,a9,N,...) N
int n = COUNT_ARGS(a, b, c); /* 3 */X-Macros Pattern
X-macros define lists once, use multiple times for different purposes. Perfect for enums with string names, error codes, opcodes, state machines. Eliminates duplication, ensures consistency. A powerful metaprogramming pattern unique to C preprocessor.
/* Define list once */
#define ERROR_LIST \
X(ERR_NONE, "No error") \
X(ERR_MEMORY, "Out of memory") \
X(ERR_FILE, "File error") \
X(ERR_NETWORK, "Network error") \
X(ERR_INVALID, "Invalid input")
/* Use 1: Create enum */
#define X(name, desc) name,
typedef enum {
ERROR_LIST
} ErrorCode;
#undef X
/* Use 2: Create string array */
#define X(name, desc) desc,
const char* error_messages[] = {
ERROR_LIST
};
#undef X
/* Usage */
void print_error(ErrorCode err) {
printf("Error: %s\n", error_messages[err]);
}
print_error(ERR_MEMORY); /* Error: Out of memory */
/* X-macro for color definitions */
#define COLOR_LIST \
X(RED, 255, 0, 0) \
X(GREEN, 0, 255, 0) \
X(BLUE, 0, 0, 255) \
X(WHITE, 255, 255, 255) \
X(BLACK, 0, 0, 0)
/* Generate enum */
#define X(name, r, g, b) COLOR_##name,
typedef enum {
COLOR_LIST
} ColorID;
#undef X
/* Generate struct array */
typedef struct {
const char *name;
unsigned char r, g, b;
} Color;
#define X(name, r, g, b) {#name, r, g, b},
Color colors[] = {
COLOR_LIST
};
#undef X
/* Generate lookup function */
const char* get_color_name(ColorID id) {
return colors[id].name;
}
/* X-macro for opcodes */
#define OPCODE_LIST \
X(OP_NOP, "nop", 0) \
X(OP_LOAD, "load", 1) \
X(OP_STORE,"store",1) \
X(OP_ADD, "add", 2) \
X(OP_SUB, "sub", 2)
/* Generate enum */
#define X(op, str, args) op,
typedef enum {
OPCODE_LIST
} Opcode;
#undef X
/* Generate dispatch table */
#define X(op, str, args) &&handle_##op,
void* dispatch_table[] = {
OPCODE_LIST
};
#undef X
/* Generate argument counts */
#define X(op, str, args) args,
int opcode_args[] = {
OPCODE_LIST
};
#undef X
/* State machine with X-macros */
#define STATE_LIST \
X(STATE_IDLE, on_idle) \
X(STATE_PROCESSING, on_processing) \
X(STATE_WAITING, on_waiting) \
X(STATE_ERROR, on_error)
/* Forward declarations */
#define X(state, handler) void handler(void);
STATE_LIST
#undef X
/* State enum */
#define X(state, handler) state,
typedef enum {
STATE_LIST
} State;
#undef X
/* Handler table */
#define X(state, handler) handler,
void (*state_handlers[])(void) = {
STATE_LIST
};
#undef X
/* Execute current state */
void execute_state(State s) {
state_handlers[s]();
}
/* Complex X-macro with multiple uses */
#define COMMAND_LIST \
X(CMD_HELP, "help", 0, "Show help") \
X(CMD_OPEN, "open", 1, "Open file") \
X(CMD_SAVE, "save", 1, "Save file") \
X(CMD_QUIT, "quit", 0, "Quit program")
/* Enum */
#define X(id, name, args, help) id,
typedef enum { COMMAND_LIST } CommandID;
#undef X
/* Name array */
#define X(id, name, args, help) name,
const char* cmd_names[] = { COMMAND_LIST };
#undef X
/* Help text */
#define X(id, name, args, help) help,
const char* cmd_help[] = { COMMAND_LIST };
#undef X
/* Argument counts */
#define X(id, name, args, help) args,
int cmd_argc[] = { COMMAND_LIST };
#undef X
/* Lookup function */
CommandID find_command(const char *name) {
#define X(id, cmdname, args, help) \
if (strcmp(name, cmdname) == 0) return id;
COMMAND_LIST
#undef X
return -1;
}Advanced Stringification and Token Manipulation
Combining # and ## creates powerful code generation patterns. Understand expansion rules: # and ## inhibit expansion, so use helper macros. These techniques enable creating DSLs and reducing boilerplate dramatically.
/* Basic stringification */
#define STR(x) #x
printf("%s\n", STR(hello)); /* "hello" */
/* Problem: Doesn't expand macros */
#define VALUE 100
printf("%s\n", STR(VALUE)); /* "VALUE" not "100" */
/* Solution: Two-level expansion */
#define XSTR(x) STR(x)
printf("%s\n", XSTR(VALUE)); /* "100" */
/* Token pasting */
#define PASTE(a, b) a##b
int PASTE(var, 123) = 42; /* var123 */
/* Problem: Doesn't expand macros */
#define PREFIX temp
int PASTE(PREFIX, _var) = 10; /* PREFIX_var (wrong!) */
/* Solution: Two-level pasting */
#define XPASTE(a, b) PASTE(a, b)
int XPASTE(PREFIX, _var) = 10; /* temp_var (correct!) */
/* Generate function declarations */
#define DECLARE_FUNC(ret, name, ...) \
ret name(__VA_ARGS__);
DECLARE_FUNC(int, add, int a, int b)
DECLARE_FUNC(void, print, const char *msg)
/* Expands to:
int add(int a, int b);
void print(const char *msg);
*/
/* Generate getters/setters */
#define PROPERTY(type, name) \
type _##name; \
type get_##name(void) { return _##name; } \
void set_##name(type value) { _##name = value; }
typedef struct {
PROPERTY(int, count)
PROPERTY(float, price)
} Item;
/* Generic min/max with type */
#define DEFINE_MIN_MAX(type) \
type min_##type(type a, type b) { \
return a < b ? a : b; \
} \
type max_##type(type a, type b) {\
return a > b ? a : b; \
}
DEFINE_MIN_MAX(int)
DEFINE_MIN_MAX(float)
/* Creates: min_int, max_int, min_float, max_float */
/* Repeat macro */
#define REPEAT_1(x) x
#define REPEAT_2(x) x x
#define REPEAT_4(x) REPEAT_2(x) REPEAT_2(x)
#define REPEAT_8(x) REPEAT_4(x) REPEAT_4(x)
int array[] = { REPEAT_4(1,) 2 }; /* {1, 1, 1, 1, 2} */
/* Comma handling tricks */
#define EMPTY()
#define COMMA() ,
/* Remove parentheses */
#define UNPAREN(...) __VA_ARGS__
#define DATA (int, float, char)
UNPAREN DATA /* Expands to: int, float, char */
/* Defer expansion */
#define EVAL(...) __VA_ARGS__
#define CAT(a, b) a##b
#define DEFER(x) x EMPTY()
DEFER(CAT)(x, y) /* Defers concatenation */
/* Map macro over arguments */
#define MAP_1(f, x) f(x)
#define MAP_2(f, x, ...) f(x), MAP_1(f, __VA_ARGS__)
#define MAP_3(f, x, ...) f(x), MAP_2(f, __VA_ARGS__)
#define SQUARE(x) ((x) * (x))
int values[] = { MAP_3(SQUARE, 1, 2, 3) };
/* {1, 4, 9} */
/* For-each macro (limited) */
#define FOR_EACH(macro, ...) \
FOR_EACH_(__VA_ARGS__, 5, 4, 3, 2, 1, 0)(macro, __VA_ARGS__)
#define FOR_EACH_(a,b,c,d,e,N,...) FOR_EACH_##N
#define FOR_EACH_0(macro, ...)
#define FOR_EACH_1(macro, x) macro(x)
#define FOR_EACH_2(macro, x, ...) macro(x) FOR_EACH_1(macro, __VA_ARGS__)
#define FOR_EACH_3(macro, x, ...) macro(x) FOR_EACH_2(macro, __VA_ARGS__)
#define PRINT_ITEM(x) printf("%s\n", #x);
FOR_EACH(PRINT_ITEM, apple, banana, cherry)
/* Conditional macros */
#define IF_1(t, f) t
#define IF_0(t, f) f
#define IF(cond, t, f) IF_##cond(t, f)
#define ENABLED 1
int value = IF(ENABLED, 10, 20); /* 10 */Code Generation and DSL Creation
Macros enable domain-specific languages within C. Create fluent APIs, test frameworks, serialization, reflection-like capabilities. Balance power with readability - complex macros make debugging harder.
/* Test framework DSL */
#define TEST(name) \
void test_##name(void); \
TestCase tc_##name = {#name, test_##name, __FILE__, __LINE__}; \
void test_##name(void)
#define ASSERT_EQ(expected, actual) \
if ((expected) != (actual)) { \
test_fail(__FILE__, __LINE__, #actual, expected, actual); \
}
/* Usage */
TEST(addition) {
ASSERT_EQ(4, 2 + 2);
ASSERT_EQ(0, 0 + 0);
}
TEST(subtraction) {
ASSERT_EQ(0, 5 - 5);
}
/* Serialization DSL */
#define BEGIN_STRUCT(name) \
typedef struct name name; \
struct name {
#define FIELD(type, name) \
type name;
#define END_STRUCT \
};
#define SERIALIZE_STRUCT(name, ...) \
void serialize_##name(name *s, FILE *f) { \
FOR_EACH_FIELD(SERIALIZE_FIELD, __VA_ARGS__) \
}
BEGIN_STRUCT(Person)
FIELD(int, age)
FIELD(char, name[50])
END_STRUCT
/* State machine DSL */
#define STATE_MACHINE(name) \
typedef enum name##_State name##_State;
#define STATES(...) \
enum name##_State { __VA_ARGS__ };
#define ON_EVENT(state, event, action) \
case state: if (event) { action; } break;
STATE_MACHINE(Door)
STATES(CLOSED, OPENING, OPEN, CLOSING)
void door_update(Door *door, int button_pressed) {
switch (door->state) {
ON_EVENT(CLOSED, button_pressed, door->state = OPENING)
ON_EVENT(OPENING, door->timer == 0, door->state = OPEN)
ON_EVENT(OPEN, button_pressed, door->state = CLOSING)
ON_EVENT(CLOSING, door->timer == 0, door->state = CLOSED)
}
}
/* Builder pattern DSL */
#define BUILDER(type) \
typedef struct type##Builder type##Builder;
#define WITH(type, field, value) \
type##_set_##field(builder, value)
BUILDER(Config)
Config* build_config(void) {
ConfigBuilder *builder = config_builder_new();
WITH(Config, port, 8080);
WITH(Config, host, "localhost");
WITH(Config, threads, 4);
return config_builder_build(builder);
}
/* Delegate pattern */
#define DELEGATE(ret, name, ...) \
ret (*name)(__VA_ARGS__);
typedef struct {
DELEGATE(void, on_connect, void *user_data)
DELEGATE(void, on_disconnect, void *user_data)
DELEGATE(int, on_message, const char *msg, void *user_data)
} NetworkCallbacks;
/* Property validation DSL */
#define VALIDATE_RANGE(var, min, max) \
if ((var) < (min) || (var) > (max)) { \
fprintf(stderr, #var " must be between " #min " and " #max "\n"); \
return 0; \
}
#define VALIDATE_NOT_NULL(var) \
if ((var) == NULL) { \
fprintf(stderr, #var " cannot be NULL\n"); \
return 0; \
}
int validate_user(User *user) {
VALIDATE_NOT_NULL(user)
VALIDATE_NOT_NULL(user->name)
VALIDATE_RANGE(user->age, 0, 150)
VALIDATE_RANGE(user->score, 0, 100)
return 1;
}
/* Switch-case generator */
#define CASE_RETURN(value) case value: return #value;
const char* opcode_to_string(Opcode op) {
switch (op) {
CASE_RETURN(OP_ADD)
CASE_RETURN(OP_SUB)
CASE_RETURN(OP_MUL)
CASE_RETURN(OP_DIV)
default: return "UNKNOWN";
}
}
/* Logging DSL */
#define LOG_LEVEL_ERROR 0
#define LOG_LEVEL_WARN 1
#define LOG_LEVEL_INFO 2
#define LOG_LEVEL_DEBUG 3
#ifndef LOG_LEVEL
#define LOG_LEVEL LOG_LEVEL_INFO
#endif
#define LOG_ERROR(msg, ...) \
log_message(LOG_LEVEL_ERROR, __FILE__, __LINE__, msg, ##__VA_ARGS__)
#define LOG_WARN(msg, ...) \
if (LOG_LEVEL >= LOG_LEVEL_WARN) \
log_message(LOG_LEVEL_WARN, __FILE__, __LINE__, msg, ##__VA_ARGS__)
#define LOG_INFO(msg, ...) \
if (LOG_LEVEL >= LOG_LEVEL_INFO) \
log_message(LOG_LEVEL_INFO, __FILE__, __LINE__, msg, ##__VA_ARGS__)
/* Smart pointer simulation */
#define SMART_PTR(type) \
typedef struct type##_ptr { \
type *ptr; \
int *ref_count; \
} type##_ptr;
#define MAKE_SMART(type, var) \
type##_ptr var = type##_ptr_create();
#define RELEASE(var) \
type##_ptr_release(&var);Macro Debugging and Best Practices
Macros are powerful but error-prone. Debug by expanding preprocessor output. Follow naming conventions, document carefully, avoid side effects. Know when NOT to use macros - inline functions and const are often better.
/* Debugging macros */
/* View expansion: gcc -E file.c */
/* Or: gcc -E file.c | less */
/* Add tracing */
#ifdef TRACE_MACROS
#define TRACE(x) printf("TRACE: " #x " -> "); x; printf("\n");
#else
#define TRACE(x) x
#endif
/* Best practices */
/* 1. Use UPPERCASE for macros */
#define MAX_SIZE 100 /* Good */
#define max_size 100 /* Confusing with variables */
/* 2. Parenthesize everything */
#define BAD(x) x * x /* Bad */
#define GOOD(x) ((x) * (x)) /* Good */
/* 3. Avoid side effects */
#define UNSAFE(x) ((x) + (x))
int i = 0;
UNSAFE(i++); /* i incremented twice! */
/* Use inline function instead */
static inline int safe_double(int x) {
return x + x;
}
/* 4. Use do-while(0) for multi-statement macros */
#define BAD_MACRO(x) \
printf("Value: %d\n", x); \
x++;
#define GOOD_MACRO(x) do { \
printf("Value: %d\n", x); \
(x)++; \
} while(0)
/* 5. Prefer const/enum over #define */
/* BAD */
#define MAX 100
/* GOOD */
enum { MAX = 100 };
const int MAX = 100;
/* 6. Prefer inline functions */
/* BAD */
#define SQUARE(x) ((x) * (x))
/* GOOD */
static inline int square(int x) {
return x * x;
}
/* 7. Document macro behavior */
/**
* Swaps two values of any type
* @note Uses typeof (GCC extension)
* @warning Evaluates arguments once but creates temp
*/
#define SWAP(a, b) do { \
typeof(a) _temp = (a); \
(a) = (b); \
(b) = _temp; \
} while(0)
/* 8. Use unique names for macro locals */
#define BAD_SWAP(a, b) { \
int temp = (a); \ /* temp may conflict */
(a) = (b); \
(b) = temp; \
}
#define GOOD_SWAP(a, b) { \
int _macro_temp_##__LINE__ = (a); \ /* Unique name */
(a) = (b); \
(b) = _macro_temp_##__LINE__; \
}
/* 9. Provide both macro and function versions */
/* For debugging: use function */
#ifdef NDEBUG
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#else
static inline int min_impl(int a, int b, const char *file, int line) {
printf("%s:%d: min(%d, %d)\n", file, line, a, b);
return a < b ? a : b;
}
#define MIN(a, b) min_impl(a, b, __FILE__, __LINE__)
#endif
/* 10. Warn about common misuse */
#ifdef __GNUC__
#define DEPRECATED_MACRO(msg) \
_Pragma("GCC warning \"" msg "\"")
#else
#define DEPRECATED_MACRO(msg)
#endif
/* When NOT to use macros */
/* Don't use for: */
/* - Type aliases (use typedef) */
/* - Simple functions (use inline) */
/* - Constants (use const/enum) */
/* - Complex logic (use real functions) */
/* DO use for: */
/* - Conditional compilation (#ifdef) */
/* - Header guards (#ifndef) */
/* - Code generation (X-macros) */
/* - Debug instrumentation */
/* - DSL creation (carefully) */
/* Macro limitations */
/* - No type checking */
/* - No namespace control */
/* - Hard to debug */
/* - Can't take address */
/* - No recursion */
/* - Obscure errors */Summary & What's Next
Key Takeaways:
- ✅ # stringifies, ## pastes tokens
- ✅ Use two-level macros for proper expansion
- ✅ X-macros eliminate duplication
- ✅ Variadic macros with __VA_ARGS__
- ✅ Macros enable DSL creation
- ✅ Debug with gcc -E (preprocessor output)
- ✅ Prefer inline functions when possible
- ✅ Document macro behavior thoroughly