C Macros & Preprocessor

Code generation and text replacement before compilation

⚙️ What are C Macros?

Macros and preprocessor directives in C provide text replacement and code generation capabilities, allowing you to create reusable code patterns and conditional compilation before the actual compilation begins.


// Simple macro definition
#define PI 3.14159
#define MAX(a,b) ((a) > (b) ? (a) : (b))
                                    

Preprocessor Features

🔄

Text Replacement

Replace text before compilation

#define SIZE 100
int arr[SIZE];  // becomes int arr[100];
🎯

Function-like Macros

Macros with parameters

#define SQUARE(x) ((x) * (x))
int result = SQUARE(5);
🔀

Conditional Compilation

Include/exclude code conditionally

#ifdef DEBUG
    printf("Debug info");
#endif
📁

File Inclusion

Include other files

#include <stdio.h>
#include "myheader.h"

🔹 Basic Macros

Macros in C are preprocessor directives that perform text substitution before compilation, enabling code reusability and compile-time constants. The #define directive creates macros like #define PI 3.14159 for constants or #define MAX(a,b) ((a)>(b)?(a):(b)) for function-like macros. Macros are processed during preprocessing, replacing every occurrence of the macro name with its definition. Unlike functions, macros have no runtime overhead since they're expanded inline. They're commonly used for defining constants, creating simple utility functions, and conditional compilation. However, macros don't perform type checking and can lead to unexpected behavior if not carefully written with proper parentheses around parameters and the entire expression.

#include <stdio.h>

// Object-like macros (constants)
#define PI 3.14159
#define MAX_SIZE 1000
#define COMPANY_NAME "TechCorp"

// Function-like macros
#define SQUARE(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))

int main() {
    printf("Company: %s\n", COMPANY_NAME);
    printf("Pi value: %.5f\n", PI);
    printf("Max array size: %d\n", MAX_SIZE);
    
    int a = 5, b = 8;
    printf("\nMacro calculations:\n");
    printf("SQUARE(%d) = %d\n", a, SQUARE(a));
    printf("MAX(%d, %d) = %d\n", a, b, MAX(a, b));
    printf("MIN(%d, %d) = %d\n", a, b, MIN(a, b));
    printf("ABS(%d) = %d\n", -7, ABS(-7));
    
    // Circle area calculation
    double radius = 5.0;
    double area = PI * SQUARE(radius);
    printf("\nCircle area (radius %.1f): %.2f\n", radius, area);
    
    return 0;
}

Output:

Company: TechCorp
Pi value: 3.14159
Max array size: 1000

Macro calculations:
SQUARE(5) = 25
MAX(5, 8) = 8
MIN(5, 8) = 5
ABS(-7) = 7

Circle area (radius 5.0): 78.54

🔹 Conditional Compilation

Conditional compilation uses preprocessor directives to include or exclude code sections based on compile-time conditions, enabling platform-specific and configurable builds. Directives like #ifdef, #ifndef, #if, #else, and #endif control which code gets compiled. For example, #ifdef DEBUG can include debugging code only when the DEBUG macro is defined. This technique is essential for writing cross-platform code with platform-specific implementations, creating debug versus release builds, managing different feature sets, and supporting multiple hardware configurations. Conditional compilation improves code flexibility without runtime performance costs, as excluded code never makes it into the final executable, keeping the binary size optimal for each specific configuration.

#include <stdio.h>

// Define debug mode
#define DEBUG 1
#define VERSION 2

// Conditional compilation based on DEBUG
#ifdef DEBUG
    #define DBG_PRINT(x) printf("DEBUG: " x "\n")
#else
    #define DBG_PRINT(x)  // Empty macro when not debugging
#endif

// Version-specific features
#if VERSION >= 2
    #define NEW_FEATURE_ENABLED 1
#else
    #define NEW_FEATURE_ENABLED 0
#endif

int main() {
    printf("Program starting...\n");
    
    DBG_PRINT("Initialization complete");
    
    int x = 10, y = 20;
    int sum = x + y;
    
    DBG_PRINT("Variables initialized");
    
    printf("Sum: %d\n", sum);
    
    #if NEW_FEATURE_ENABLED
        printf("New feature is available!\n");
        printf("Enhanced calculation: %d\n", sum * 2);
    #else
        printf("Using basic version\n");
    #endif
    
    #ifdef DEBUG
        printf("Debug mode is ON\n");
        printf("Compiled with DEBUG flag\n");
    #endif
    
    #ifndef RELEASE
        printf("This is not a release build\n");
    #endif
    
    return 0;
}

Output:

Program starting...
DEBUG: Initialization complete
DEBUG: Variables initialized
Sum: 30
New feature is available!
Enhanced calculation: 60
Debug mode is ON
Compiled with DEBUG flag
This is not a release build

🔹 Advanced Macro Techniques

Advanced macro techniques leverage powerful preprocessor features like stringification, token pasting, and variadic macros for sophisticated code generation. The stringification operator # converts macro parameters to string literals, while the token pasting operator ## concatenates tokens. For example, #define PRINT(x) printf(#x " = %d\n", x) combines both features. Variadic macros using ... and __VA_ARGS__ accept variable numbers of arguments, similar to printf. These techniques enable powerful metaprogramming capabilities like automatic logging, assertion systems, and code generation. However, they require careful use as they can make code harder to debug and understand, so they're best reserved for situations where their benefits clearly outweigh the added complexity.

#include <stdio.h>

// Stringification operator (#)
#define STRINGIFY(x) #x
#define PRINT_VAR(var) printf(#var " = %d\n", var)

// Token pasting operator (##)
#define CONCAT(a, b) a##b
#define DECLARE_VAR(type, name) type CONCAT(var_, name)

// Multi-line macros using backslash
#define SWAP(a, b) do { \
    typeof(a) temp = a; \
    a = b; \
    b = temp; \
} while(0)

// Macro with variable arguments (C99)
#define LOG(format, ...) printf("[LOG] " format "\n", ##__VA_ARGS__)

int main() {
    // Stringification example
    printf("Stringified: %s\n", STRINGIFY(Hello World));
    
    int age = 25;
    PRINT_VAR(age);  // Expands to: printf("age" " = %d\n", age);
    
    // Token pasting example
    DECLARE_VAR(int, counter);  // Creates: int var_counter;
    CONCAT(var_, counter) = 100;
    printf("var_counter = %d\n", var_counter);
    
    // Swap macro example
    int x = 10, y = 20;
    printf("Before swap: x=%d, y=%d\n", x, y);
    SWAP(x, y);
    printf("After swap: x=%d, y=%d\n", x, y);
    
    // Variable argument macro
    LOG("Program started");
    LOG("Processing %d items", 5);
    LOG("User %s logged in with ID %d", "Alice", 123);
    
    // Predefined macros
    printf("\nPredefined macros:\n");
    printf("File: %s\n", __FILE__);
    printf("Line: %d\n", __LINE__);
    printf("Date: %s\n", __DATE__);
    printf("Time: %s\n", __TIME__);
    
    return 0;
}

Output:

Stringified: Hello World
age = 25
var_counter = 100
Before swap: x=10, y=20
After swap: x=20, y=10
[LOG] Program started
[LOG] Processing 5 items
[LOG] User Alice logged in with ID 123

Predefined macros:
File: example.c
Line: 45
Date: Dec 25 2023
Time: 10:30:15

🔹 Macro Best Practices

Following macro best practices helps avoid common pitfalls like unexpected behavior, type safety issues, and debugging difficulties. Always use uppercase names for macros to distinguish them from functions and variables. Enclose macro parameters and the entire expression in parentheses to prevent operator precedence problems, like #define SQUARE(x) ((x)*(x)) instead of #define SQUARE(x) x*x. Be cautious with side effects since macro arguments can be evaluated multiple times. Prefer inline functions or const variables when type safety and debugging matter more than preprocessing capabilities. Avoid creating multi-statement macros without proper do-while wrapping. Document complex macros thoroughly, and consider whether a regular function might be clearer and safer for your specific use case.

#include <stdio.h>

// BAD: Unsafe macro (no parentheses)
#define BAD_SQUARE(x) x * x

// GOOD: Safe macro with parentheses
#define GOOD_SQUARE(x) ((x) * (x))

// BAD: Multiple evaluation side effects
#define BAD_MAX(a, b) ((a) > (b) ? (a) : (b))

// GOOD: Using GCC statement expressions (GCC extension)
#define SAFE_MAX(a, b) ({ \
    typeof(a) _a = (a); \
    typeof(b) _b = (b); \
    (_a > _b) ? _a : _b; \
})

// Multi-statement macro using do-while(0)
#define SAFE_PRINT_AND_INCREMENT(var) do { \
    printf("Value: %d\n", var); \
    var++; \
} while(0)

int increment() {
    static int count = 0;
    return ++count;
}

int main() {
    printf("Macro Safety Demonstration:\n");
    printf("===========================\n");
    
    // Problem 1: Operator precedence
    int result1 = BAD_SQUARE(3 + 2);   // Expands to: 3 + 2 * 3 + 2 = 11
    int result2 = GOOD_SQUARE(3 + 2);  // Expands to: ((3 + 2) * (3 + 2)) = 25
    
    printf("BAD_SQUARE(3 + 2) = %d (wrong!)\n", result1);
    printf("GOOD_SQUARE(3 + 2) = %d (correct)\n", result2);
    
    // Problem 2: Multiple evaluation
    printf("\nMultiple evaluation problem:\n");
    int x = 5;
    printf("x before BAD_MAX: %d\n", x);
    int max1 = BAD_MAX(x++, 10);  // x is incremented twice!
    printf("BAD_MAX result: %d, x after: %d\n", max1, x);
    
    x = 5;  // Reset
    printf("x before SAFE_MAX: %d\n", x);
    int max2 = SAFE_MAX(x++, 10);  // x is incremented only once
    printf("SAFE_MAX result: %d, x after: %d\n", max2, x);
    
    // Problem 3: Function call side effects
    printf("\nFunction call side effects:\n");
    printf("increment() calls with BAD_MAX:\n");
    int bad_result = BAD_MAX(increment(), 5);  // increment() called multiple times
    printf("Result: %d\n", bad_result);
    
    printf("increment() calls with SAFE_MAX:\n");
    int safe_result = SAFE_MAX(increment(), 5);  // increment() called once
    printf("Result: %d\n", safe_result);
    
    // Safe multi-statement macro
    printf("\nSafe multi-statement macro:\n");
    int counter = 10;
    if (counter > 5)
        SAFE_PRINT_AND_INCREMENT(counter);  // Works correctly in if statement
    
    printf("Counter after macro: %d\n", counter);
    
    return 0;
}

Output:

Macro Safety Demonstration:
===========================
BAD_SQUARE(3 + 2) = 11 (wrong!)
GOOD_SQUARE(3 + 2) = 25 (correct)

Multiple evaluation problem:
x before BAD_MAX: 5
BAD_MAX result: 10, x after: 7
x before SAFE_MAX: 5
SAFE_MAX result: 10, x after: 6

Function call side effects:
increment() calls with BAD_MAX:
Result: 2
increment() calls with SAFE_MAX:
Result: 5

Safe multi-statement macro:
Value: 10
Counter after macro: 11

🧠 Test Your Knowledge

Which operator is used for stringification in macros?