Docs

README

Macros and Definitions in C

Table of Contents

  1. •Introduction
  2. •Object-like Macros
  3. •Function-like Macros
  4. •Stringification Operator
  5. •Token Pasting Operator
  6. •Variadic Macros
  7. •Macro Best Practices
  8. •Common Pitfalls and Solutions
  9. •Predefined Macros
  10. •Practical Applications

Introduction

Macros are one of the most powerful features of the C preprocessor. They allow you to define constants, create inline code substitutions, and build complex code generation patterns. Understanding macros is essential for writing efficient, maintainable, and portable C code.

What is a Macro?

A macro is a fragment of code that is given a name. When the preprocessor encounters this name in the source code, it replaces it with the defined fragment before compilation. This process is called macro expansion.

#define PI 3.14159265359
// Every occurrence of PI will be replaced with 3.14159265359

Why Use Macros?

  1. •Symbolic Constants: Replace magic numbers with meaningful names
  2. •Code Reuse: Define common patterns once
  3. •Conditional Compilation: Enable/disable features at compile time
  4. •Performance: Avoid function call overhead for simple operations
  5. •Portability: Abstract platform-specific code

Macros vs. Functions

FeatureMacrosFunctions
ExpansionCompile-timeRuntime
Type CheckingNoneYes
OverheadNo call overheadCall/return overhead
Code SizeMay increaseSingle copy
DebuggingHarderEasier
Side EffectsCan be problematicPredictable

Object-like Macros

Object-like macros are the simplest form of macros. They define a name that represents a value or code fragment.

Basic Syntax

#define NAME replacement_text

Defining Constants

// Numeric constants
#define MAX_SIZE 100
#define PI 3.14159265359
#define BUFFER_SIZE 1024

// Character constants
#define NEWLINE '\n'
#define TAB '\t'

// String constants
#define VERSION "1.0.0"
#define PROGRAM_NAME "MyApp"

// Expression constants
#define DOUBLE_BUFFER (BUFFER_SIZE * 2)
#define ARRAY_SIZE (sizeof(array) / sizeof(array[0]))

Multi-line Macros

Use backslash (\) to continue a macro on the next line:

#define LONG_MESSAGE "This is a very long message that \
                      spans multiple lines in the source \
                      but is a single string"

#define ERROR_HEADER "===============================\n\
                      ERROR REPORT\n\
                      ===============================\n"

Empty Macros

Macros can be defined without a value for conditional compilation:

#define DEBUG          // Defined but empty
#define FEATURE_ENABLED

#ifdef DEBUG
    // This code is included
#endif

Undefining Macros

Use #undef to remove a macro definition:

#define TEMPORARY_VALUE 100
// Use TEMPORARY_VALUE...
#undef TEMPORARY_VALUE
// TEMPORARY_VALUE is no longer defined

Redefining Macros

#define MAX 100

// To safely redefine:
#undef MAX
#define MAX 200

// Or check first:
#ifndef MAX
    #define MAX 100
#endif

Function-like Macros

Function-like macros take parameters and can perform operations on them.

Basic Syntax

#define NAME(parameters) replacement_text

Important: No space between macro name and opening parenthesis!

#define SQUARE(x) ((x) * (x))     // Correct
#define SQUARE (x) ((x) * (x))    // Wrong! Treated as object-like macro

Simple Function-like Macros

// Math operations
#define SQUARE(x) ((x) * (x))
#define CUBE(x) ((x) * (x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define ABS(x) ((x) < 0 ? -(x) : (x))
#define CLAMP(x, low, high) (MIN(MAX(x, low), high))

// Type-specific operations
#define SWAP(type, a, b) do { type temp = a; a = b; b = temp; } while(0)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

// Bit operations
#define SET_BIT(n, bit) ((n) | (1 << (bit)))
#define CLEAR_BIT(n, bit) ((n) & ~(1 << (bit)))
#define TOGGLE_BIT(n, bit) ((n) ^ (1 << (bit)))
#define CHECK_BIT(n, bit) (((n) >> (bit)) & 1)

Why Use Parentheses?

Parentheses prevent operator precedence issues:

// Bad: No parentheses
#define DOUBLE(x) x * 2
int result = DOUBLE(3 + 4);  // Expands to: 3 + 4 * 2 = 11 (wrong!)

// Good: With parentheses
#define DOUBLE(x) ((x) * 2)
int result = DOUBLE(3 + 4);  // Expands to: ((3 + 4) * 2) = 14 (correct!)

Multi-statement Macros

Use do { ... } while(0) for multi-statement macros:

// Problematic:
#define LOG_ERROR(msg) printf("Error: "); printf("%s\n", msg)

if (error)
    LOG_ERROR("something wrong");  // Only first printf is conditional!

// Better:
#define LOG_ERROR(msg) do { \
    printf("Error: "); \
    printf("%s\n", msg); \
} while(0)

if (error)
    LOG_ERROR("something wrong");  // Both statements are conditional

Why do { ... } while(0)?

  1. •Creates a single statement from multiple statements
  2. •Requires a semicolon after use (natural C syntax)
  3. •Works correctly in all control structures
  4. •Compiler optimizes away the loop (zero overhead)

Stringification Operator (#)

The # operator converts a macro parameter to a string literal.

Basic Usage

#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)

printf("%s\n", STRINGIFY(Hello World));  // Prints: Hello World
printf("%s\n", STRINGIFY(100 + 200));    // Prints: 100 + 200

Two-Level Stringification

To stringify a macro's value (not its name), use two levels:

#define VERSION 100
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)

printf("%s\n", STRINGIFY(VERSION));   // Prints: VERSION
printf("%s\n", TOSTRING(VERSION));    // Prints: 100

Practical Applications

// Variable name as string
#define PRINT_VAR(var) printf(#var " = %d\n", var)

int count = 42;
PRINT_VAR(count);  // Prints: count = 42

// Assert macro
#define ASSERT(condition) do { \
    if (!(condition)) { \
        fprintf(stderr, "Assertion failed: %s\n", #condition); \
        fprintf(stderr, "File: %s, Line: %d\n", __FILE__, __LINE__); \
        abort(); \
    } \
} while(0)

ASSERT(x > 0);  // If fails: "Assertion failed: x > 0"

// Debug printing
#define DEBUG_PRINT(expr) printf(#expr " = %d\n", (expr))
DEBUG_PRINT(a + b * c);  // Prints: a + b * c = <value>

Token Pasting Operator (##)

The ## operator concatenates two tokens to form a new token.

Basic Usage

#define CONCAT(a, b) a ## b

int CONCAT(my, Var) = 10;    // Creates: int myVar = 10;
int CONCAT(count, 1) = 100;  // Creates: int count1 = 100;

Practical Applications

// Generate unique variable names
#define UNIQUE_VAR(name) CONCAT(name, __LINE__)

int UNIQUE_VAR(temp);  // Creates: int temp42 (if on line 42)

// Create function names
#define DECLARE_GETTER(type, name) \
    type get_##name(void) { return name; }

static int score = 100;
DECLARE_GETTER(int, score)  // Creates: int get_score(void) { return score; }

// Type-generic functions
#define DEFINE_PRINT_FUNC(type, format) \
    void print_##type(type value) { \
        printf(format, value); \
    }

DEFINE_PRINT_FUNC(int, "%d\n")
DEFINE_PRINT_FUNC(float, "%f\n")
DEFINE_PRINT_FUNC(char, "%c\n")

// Creates:
// void print_int(int value) { printf("%d\n", value); }
// void print_float(float value) { printf("%f\n", value); }
// void print_char(char value) { printf("%c\n", value); }

Combining # and

#define ENUM_ITEM(name) name,
#define ENUM_STRING(name) #name,

#define COLORS \
    X(RED) \
    X(GREEN) \
    X(BLUE)

// Define enum
#define X(name) ENUM_ITEM(name)
enum Color { COLORS };
#undef X

// Define string array
#define X(name) ENUM_STRING(name)
const char* color_names[] = { COLORS };
#undef X

// color_names[RED] returns "RED"

Variadic Macros

Variadic macros accept a variable number of arguments.

Basic Syntax

#define MACRO_NAME(fixed_args, ...) replacement_text(__VA_ARGS__)

Simple Variadic Macros

// Print with format
#define PRINTF(...) printf(__VA_ARGS__)

PRINTF("Hello\n");
PRINTF("Value: %d\n", 42);
PRINTF("%s = %d\n", "x", 10);

// Print with prefix
#define LOG(format, ...) printf("[LOG] " format, __VA_ARGS__)

LOG("Value: %d\n", 42);  // [LOG] Value: 42

Handling Empty Variable Arguments

// Problem: LOG("Simple message") fails because __VA_ARGS__ is empty

// Solution 1: Use ##__VA_ARGS__ (GCC extension)
#define LOG(format, ...) printf("[LOG] " format, ##__VA_ARGS__)

// Solution 2: C99 approach - always include at least one argument
#define LOG(format, ...) printf("[LOG] " format __VA_OPT__(,) __VA_ARGS__)

// Solution 3: First argument as format
#define LOG(...) printf("[LOG] " __VA_ARGS__)

Advanced Variadic Macros

// Debug logging with file and line
#define DEBUG(format, ...) do { \
    fprintf(stderr, "[DEBUG %s:%d] " format "\n", \
            __FILE__, __LINE__, ##__VA_ARGS__); \
} while(0)

DEBUG("Starting process");
DEBUG("Processing item %d of %d", i, total);

// Conditional logging
#ifdef ENABLE_LOGGING
    #define LOG(level, ...) do { \
        printf("[%s] ", level); \
        printf(__VA_ARGS__); \
        printf("\n"); \
    } while(0)
#else
    #define LOG(level, ...) ((void)0)
#endif

LOG("INFO", "Application started");
LOG("ERROR", "Failed to open file: %s", filename);

Counting Variable Arguments

// Count number of arguments (up to 10)
#define COUNT_ARGS(...) \
    COUNT_ARGS_IMPL(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
#define COUNT_ARGS_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N

int count = COUNT_ARGS(a, b, c);  // count = 3

Macro Best Practices

1. Use Uppercase for Macro Names

#define MAX_BUFFER_SIZE 1024  // Good
#define maxBufferSize 1024    // Bad - looks like a variable

2. Always Parenthesize

// Parenthesize the entire expression
#define AREA(w, h) ((w) * (h))

// Parenthesize each parameter
#define SQUARE(x) ((x) * (x))

// Parenthesize both
#define DOUBLE(x) (((x)) * 2)  // Overkill but safe

3. Use Inline Functions When Possible (C99+)

// Prefer inline functions for type safety
static inline int square(int x) {
    return x * x;
}

// Use macros when type-generic behavior is needed
#define SQUARE(x) ((x) * (x))  // Works with int, float, double

4. Document Complex Macros

/**
 * @brief Safely allocates memory and checks for failure
 * @param ptr Pointer variable to allocate to
 * @param type The type to allocate
 * @param count Number of elements to allocate
 * @note Calls handle_allocation_failure() on failure
 */
#define SAFE_ALLOC(ptr, type, count) do { \
    (ptr) = (type *)malloc(sizeof(type) * (count)); \
    if ((ptr) == NULL) { \
        handle_allocation_failure(__FILE__, __LINE__); \
    } \
} while(0)

5. Avoid Side Effects

// Dangerous with side effects
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int result = MAX(x++, y++);  // x or y incremented twice!

// Safe version using GCC extension
#define MAX_SAFE(a, b) ({ \
    __typeof__(a) _a = (a); \
    __typeof__(b) _b = (b); \
    _a > _b ? _a : _b; \
})

6. Use Unique Names for Internal Variables

// Potential conflict
#define SWAP(a, b) do { int temp = a; a = b; b = temp; } while(0)
int temp = 10;  // Conflicts!
SWAP(x, temp);  // Broken!

// Safe version
#define SWAP(a, b) do { \
    int _swap_temp_##__LINE__ = (a); \
    (a) = (b); \
    (b) = _swap_temp_##__LINE__; \
} while(0)

Common Pitfalls and Solutions

Pitfall 1: Missing Parentheses

// Problem
#define MULTIPLY(a, b) a * b
int result = MULTIPLY(2 + 3, 4 + 5);  // = 2 + 3 * 4 + 5 = 19 (wrong!)

// Solution
#define MULTIPLY(a, b) ((a) * (b))
int result = MULTIPLY(2 + 3, 4 + 5);  // = ((2 + 3) * (4 + 5)) = 45 (correct!)

Pitfall 2: Double Evaluation

// Problem
#define SQUARE(x) ((x) * (x))
int result = SQUARE(expensive_function());  // Called twice!

// Solution 1: Use inline function
static inline int square(int x) { return x * x; }

// Solution 2: GCC extension
#define SQUARE_SAFE(x) ({ \
    __typeof__(x) _x = (x); \
    _x * _x; \
})

Pitfall 3: Macro Expansion Order

// Problem
#define A 1
#define B A
#define A 2
// B is still 1, not 2 (A expanded when B was defined)

// Solution: Two-level macros
#define A 1
#define B() A  // Function-like defers expansion
#define A 2
// B() now returns 2

Pitfall 4: Semicolon Issues

// Problem
#define DO_SOMETHING() statement1; statement2

if (condition)
    DO_SOMETHING();  // Only statement1 is conditional!

// Solution
#define DO_SOMETHING() do { statement1; statement2; } while(0)

Pitfall 5: Spaces in Macro Definition

// Problem
#define FUNC (x) ((x) * 2)  // Space makes it object-like!
// Expands to: (x) ((x) * 2), not a function call

// Solution
#define FUNC(x) ((x) * 2)  // No space before parenthesis

Pitfall 6: Recursive Macros

// This won't work as expected
#define FOO (4 + FOO)  // FOO is not recursively expanded

// Solution: Macros cannot be truly recursive
// Use functions for recursive behavior

Predefined Macros

C compilers provide several predefined macros:

Standard Macros (ANSI C)

MacroDescription
__FILE__Current source file name (string)
__LINE__Current line number (integer)
__DATE__Compilation date "Mmm dd yyyy"
__TIME__Compilation time "hh:mm:ss"
__STDC__1 if compiler is ISO C compliant
__STDC_VERSION__C standard version (e.g., 199901L for C99)

GCC-Specific Macros

MacroDescription
__GNUC__GCC major version
__GNUC_MINOR__GCC minor version
__FUNCTION__Current function name
__PRETTY_FUNCTION__Function name with signature
__COUNTER__Unique incrementing counter

Platform Detection

// Operating system
#ifdef _WIN32
    // Windows
#elif defined(__linux__)
    // Linux
#elif defined(__APPLE__)
    // macOS
#endif

// Architecture
#ifdef __x86_64__
    // 64-bit x86
#elif defined(__i386__)
    // 32-bit x86
#elif defined(__arm__)
    // ARM
#endif

Usage Examples

// Logging with file and line
#define LOG(msg) printf("[%s:%d] %s\n", __FILE__, __LINE__, msg)

// Compile-time information
printf("Compiled on %s at %s\n", __DATE__, __TIME__);
printf("Using C standard: %ld\n", __STDC_VERSION__);

// Function tracing
#define TRACE() printf("Entering %s\n", __FUNCTION__)

void my_function(void) {
    TRACE();  // Prints: Entering my_function
    // ...
}

// Build information
const char* build_info = "Built " __DATE__ " " __TIME__ " from " __FILE__;

Practical Applications

Debug Macros

#ifdef DEBUG
    #define DEBUG_PRINT(fmt, ...) \
        fprintf(stderr, "[DEBUG %s:%d] " fmt "\n", \
                __FILE__, __LINE__, ##__VA_ARGS__)
    #define DEBUG_ASSERT(cond) do { \
        if (!(cond)) { \
            fprintf(stderr, "Assertion failed: %s\n", #cond); \
            fprintf(stderr, "Location: %s:%d\n", __FILE__, __LINE__); \
            abort(); \
        } \
    } while(0)
    #define DEBUG_TRACE() \
        fprintf(stderr, "[TRACE] %s() called\n", __FUNCTION__)
#else
    #define DEBUG_PRINT(fmt, ...) ((void)0)
    #define DEBUG_ASSERT(cond) ((void)0)
    #define DEBUG_TRACE() ((void)0)
#endif

Error Handling Macros

#define CHECK_NULL(ptr) do { \
    if ((ptr) == NULL) { \
        fprintf(stderr, "Null pointer: %s\n", #ptr); \
        return -1; \
    } \
} while(0)

#define CHECK_ALLOC(ptr) do { \
    if ((ptr) == NULL) { \
        perror("Memory allocation failed"); \
        exit(EXIT_FAILURE); \
    } \
} while(0)

#define SAFE_FREE(ptr) do { \
    if ((ptr) != NULL) { \
        free(ptr); \
        (ptr) = NULL; \
    } \
} while(0)

Container Macros

// Get container from member pointer
#define container_of(ptr, type, member) \
    ((type *)((char *)(ptr) - offsetof(type, member)))

// Array utilities
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#define ARRAY_END(arr) ((arr) + ARRAY_SIZE(arr))

// Loop helpers
#define FOREACH(item, array) \
    for (typeof((array)[0]) *item = (array); \
         item < ARRAY_END(array); \
         item++)

#define REPEAT(n) for (int _i = 0; _i < (n); _i++)

Type-Safe Generic Operations

// Generic swap using GCC extension
#define SWAP(a, b) do { \
    __typeof__(a) _tmp = (a); \
    (a) = (b); \
    (b) = _tmp; \
} while(0)

// Generic min/max
#define MIN(a, b) ({ \
    __typeof__(a) _a = (a); \
    __typeof__(b) _b = (b); \
    _a < _b ? _a : _b; \
})

#define MAX(a, b) ({ \
    __typeof__(a) _a = (a); \
    __typeof__(b) _b = (b); \
    _a > _b ? _a : _b; \
})

Enum-String Mapping (X-Macro Technique)

// Define the list once
#define STATUS_LIST \
    X(STATUS_OK, "OK") \
    X(STATUS_ERROR, "Error") \
    X(STATUS_PENDING, "Pending") \
    X(STATUS_COMPLETE, "Complete")

// Generate enum
#define X(name, str) name,
typedef enum { STATUS_LIST } Status;
#undef X

// Generate string array
#define X(name, str) [name] = str,
const char* status_strings[] = { STATUS_LIST };
#undef X

// Usage
Status s = STATUS_OK;
printf("Status: %s\n", status_strings[s]);  // "OK"

Summary

Key Points

  1. •Object-like macros define simple text substitutions
  2. •Function-like macros accept parameters and perform operations
  3. •Stringification (#) converts macro parameters to strings
  4. •Token pasting (##) concatenates tokens to form new identifiers
  5. •Variadic macros accept variable numbers of arguments

Best Practices Checklist

  • • Use uppercase names for macros
  • • Parenthesize all parameters and the entire expression
  • • Use do { ... } while(0) for multi-statement macros
  • • Avoid side effects in macro arguments
  • • Document complex macros thoroughly
  • • Consider inline functions as alternatives
  • • Use unique names for internal variables
  • • Test macros with edge cases

When to Use Macros vs. Functions

Use Macros WhenUse Functions When
Need compile-time constantsNeed type safety
Need type-generic operationsNeed debugging support
Need code at specific locationHave complex logic
Need zero runtime overheadNeed recursion
Need stringification/pastingHave side effects in args

Further Reading

  • •"The C Programming Language" by Kernighan and Ritchie
  • •"C: A Reference Manual" by Harbison and Steele
  • •GCC Preprocessor Documentation
  • •C99/C11 Standard Preprocessor Specifications

This document is part of the C Programming Language course. For practical exercises, see the accompanying exercises.c file.

README - C Programming Tutorial | DeepML