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