C Programming: Low-Level Mastery
HomeInsightsCoursesC ProgrammingAdvanced Macro Techniques
Preprocessor

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.

C
#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.

C
/* 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.

C
/* 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 &gt; 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.

C
/* 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-&gt;state) {
        ON_EVENT(CLOSED, button_pressed, door-&gt;state = OPENING)
        ON_EVENT(OPENING, door-&gt;timer == 0, door-&gt;state = OPEN)
        ON_EVENT(OPEN, button_pressed, door-&gt;state = CLOSING)
        ON_EVENT(CLOSING, door-&gt;timer == 0, door-&gt;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) &gt; (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-&gt;name)
    VALIDATE_RANGE(user-&gt;age, 0, 150)
    VALIDATE_RANGE(user-&gt;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.

C
/* 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 " -&gt; "); 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

What's Next?

Let's learn about header file organization and include best practices!