Preprocessor Macros in C: Powerful Code Generation and Conditional Compilation

The C preprocessor is a powerful text-processing tool that runs before the compiler, transforming source code through macros, conditional compilation, and file inclusion. Preprocessor macros enable code generation, platform adaptation, debugging aids, and performance optimizations that would be impossible or cumbersome with pure C. For C programmers, mastering preprocessor macros is essential for writing portable, efficient, and maintainable code.

What are Preprocessor Macros?

Preprocessor macros are text substitutions performed by the C preprocessor before compilation begins. They are defined using the #define directive and can be simple constant replacements or function-like macros with parameters. The preprocessor also provides conditional compilation (#if, #ifdef), file inclusion (#include), and other directives that control how source code is processed.

Why Preprocessor Macros are Essential in C

  1. Code Generation: Create repetitive code patterns automatically
  2. Platform Portability: Adapt code to different operating systems and compilers
  3. Debugging Support: Include debug code only in development builds
  4. Performance: Eliminate function call overhead with inline code
  5. Configuration: Define compile-time constants and settings
  6. Code Clarity: Create expressive shortcuts for complex expressions

Basic Macro Definitions

#include <stdio.h>
// ============================================================
// SIMPLE OBJECT-LIKE MACROS
// ============================================================
// Constants
#define PI 3.141592653589793
#define MAX_SIZE 100
#define APPLICATION_NAME "My Program"
#define VERSION "1.0.0"
// Simple expressions
#define AREA_OF_CIRCLE(r) (PI * (r) * (r))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define ABS(x) ((x) < 0 ? -(x) : (x))
#define SQUARE(x) ((x) * (x))
#define CUBE(x) ((x) * (x) * (x))
int main() {
printf("=== Simple Macro Definitions ===\n\n");
printf("APPLICATION_NAME: %s\n", APPLICATION_NAME);
printf("VERSION: %s\n", VERSION);
printf("PI: %f\n", PI);
printf("MAX_SIZE: %d\n\n", MAX_SIZE);
double radius = 5.0;
printf("Circle with radius %.2f:\n", radius);
printf("  Area: %.2f\n", AREA_OF_CIRCLE(radius));
int a = 10, b = 20;
printf("\nMAX(%d, %d) = %d\n", a, b, MAX(a, b));
printf("MIN(%d, %d) = %d\n", a, b, MIN(a, b));
int x = -5;
printf("ABS(%d) = %d\n", x, ABS(x));
printf("SQUARE(%d) = %d\n", 5, SQUARE(5));
printf("CUBE(%d) = %d\n", 3, CUBE(3));
return 0;
}

Function-Like Macros

#include <stdio.h>
// ============================================================
// FUNCTION-LIKE MACROS
// ============================================================
// Simple function-like macros
#define IS_EVEN(n) ((n) % 2 == 0)
#define IS_ODD(n) ((n) % 2 != 0)
#define IS_POSITIVE(x) ((x) > 0)
#define IS_NEGATIVE(x) ((x) < 0)
// Macros with multiple statements
#define PRINT_INT(x) printf(#x " = %d\n", x)
#define PRINT_DOUBLE(x) printf(#x " = %f\n", x)
#define PRINT_STRING(x) printf(#x " = \"%s\"\n", x)
// Macros with loops
#define FOR_I_FROM_TO(start, end) \
for (int i = (start); i < (end); i++)
#define FOR_J_FROM_TO(start, end) \
for (int j = (start); j < (end); j++)
// Macros that create temporary variables
#define SWAP(a, b, type) do { \
type temp = a; \
a = b; \
b = temp; \
} while (0)
// Macros with variable arguments (C99)
#define DEBUG_PRINT(fmt, ...) \
printf("DEBUG [%s:%d]: " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#define ERROR_PRINT(fmt, ...) \
fprintf(stderr, "ERROR [%s:%d]: " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
// Macro to compute array size
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
int main() {
printf("=== Function-Like Macros ===\n\n");
int num = 42;
PRINT_INT(num);
PRINT_INT(100);
printf("\nIS_EVEN(%d): %s\n", num, IS_EVEN(num) ? "yes" : "no");
printf("IS_ODD(%d): %s\n", num, IS_ODD(num) ? "yes" : "no");
// Using loop macro
printf("\nUsing FOR_I_FROM_TO:\n");
FOR_I_FROM_TO(0, 5) {
printf("i = %d\n", i);
}
// SWAP macro
int x = 10, y = 20;
printf("\nBefore swap: x = %d, y = %d\n", x, y);
SWAP(x, y, int);
printf("After swap:  x = %d, y = %d\n", x, y);
// DEBUG_PRINT with variable arguments
DEBUG_PRINT("Value of num = %d", num);
DEBUG_PRINT("x = %d, y = %d", x, y);
// ERROR_PRINT
ERROR_PRINT("Division by zero at line %d", __LINE__);
// ARRAY_SIZE macro
int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
printf("\nArray size: %zu\n", ARRAY_SIZE(array));
return 0;
}

Stringification and Token Pasting

#include <stdio.h>
// ============================================================
// STRINGIFICATION (#) AND TOKEN PASTING (##)
// ============================================================
// Stringification operator (#) converts parameter to string literal
#define STRINGIFY(x) #x
#define TO_STRING(x) STRINGIFY(x)
#define PRINT_VAR(var) printf(#var " = %d\n", var)
// Token pasting operator (##) concatenates tokens
#define CONCAT(a, b) a##b
#define MAKE_VAR(name, num) name##num
// Creating variable names with token pasting
#define CREATE_VARS(prefix) \
int prefix##1; \
int prefix##2; \
int prefix##3;
// Generic print macro using stringification
#define PRINT(x) _Generic((x), \
int: printf("%s = %d\n", #x, x), \
double: printf("%s = %f\n", #x, x), \
char*: printf("%s = %s\n", #x, x) \
)
// Macro to define structure and access functions
#define DEFINE_POINT(type) \
typedef struct { \
type x; \
type y; \
} Point_##type; \
\
void print_point_##type(Point_##type *p) { \
printf(#type " point: (%f, %f)\n", (double)p->x, (double)p->y); \
}
DEFINE_POINT(int)
DEFINE_POINT(double)
int main() {
printf("=== Stringification and Token Pasting ===\n\n");
// Stringification
printf("STRINGIFY(123) = %s\n", STRINGIFY(123));
printf("STRINGIFY(hello) = %s\n", STRINGIFY(hello));
printf("TO_STRING(42) = %s\n", TO_STRING(42));
// PRINT_VAR
int value = 99;
PRINT_VAR(value);
// Token pasting
int xy = 123;
printf("CONCAT(x, y) = %d\n", CONCAT(x, y));
// Creating variables with token pasting
CREATE_VARS(test);
test1 = 10;
test2 = 20;
test3 = 30;
printf("test1 = %d, test2 = %d, test3 = %d\n", test1, test2, test3);
// Generic print
int i = 42;
double d = 3.14159;
char *s = "Hello";
PRINT(i);
PRINT(d);
PRINT(s);
// Using generated structures
Point_int p1 = {10, 20};
Point_double p2 = {1.5, 2.5};
print_point_int(&p1);
print_point_double(&p2);
return 0;
}

Conditional Compilation

#include <stdio.h>
// ============================================================
// CONDITIONAL COMPILATION
// ============================================================
// Simple conditionals
#define DEBUG 1
#define PLATFORM_LINUX 1
// #define PLATFORM_WINDOWS
// Conditional compilation based on macros
#if DEBUG
#define LOG(msg) printf("LOG: %s\n", msg)
#define LOG_VAR(fmt, var) printf("LOG: " fmt "\n", var)
#else
#define LOG(msg) ((void)0)
#define LOG_VAR(fmt, var) ((void)0)
#endif
// Platform-specific code
#ifdef PLATFORM_LINUX
#include <unistd.h>
#define CLEAR_SCREEN() printf("\033[2J\033[1;1H")
#define SLEEP(ms) usleep((ms) * 1000)
#elif defined(PLATFORM_WINDOWS)
#include <windows.h>
#define CLEAR_SCREEN() system("cls")
#define SLEEP(ms) Sleep(ms)
#else
#define CLEAR_SCREEN() printf("\n\n\n")
#define SLEEP(ms) /* unsupported */
#endif
// Feature detection
#if __STDC_VERSION__ >= 201112L
#define HAVE_C11 1
#else
#define HAVE_C11 0
#endif
#if defined(__GNUC__) || defined(__clang__)
#define COMPILER_GNU_LIKE 1
#elif defined(_MSC_VER)
#define COMPILER_MSVC 1
#endif
// Compiler-specific attributes
#ifdef COMPILER_GNU_LIKE
#define DEPRECATED(func) func __attribute__((deprecated))
#define UNUSED __attribute__((unused))
#define PACKED __attribute__((packed))
#elif defined(COMPILER_MSVC)
#define DEPRECATED(func) __declspec(deprecated) func
#define UNUSED
#define PACKED __pragma(pack(push, 1))
#endif
// Version checking
#define VERSION_MAJOR 1
#define VERSION_MINOR 0
#define VERSION_PATCH 5
#define VERSION_GE(major, minor, patch) \
(VERSION_MAJOR > (major) || \
(VERSION_MAJOR == (major) && VERSION_MINOR > (minor)) || \
(VERSION_MAJOR == (major) && VERSION_MINOR == (minor) && VERSION_PATCH >= (patch)))
int main() {
printf("=== Conditional Compilation ===\n\n");
// Debug logging
LOG("Application started");
int counter = 42;
LOG_VAR("counter = %d", counter);
// Platform-specific
printf("Platform-specific features:\n");
#ifdef PLATFORM_LINUX
printf("  Running on Linux\n");
#elif defined(PLATFORM_WINDOWS)
printf("  Running on Windows\n");
#else
printf("  Unknown platform\n");
#endif
// Feature detection
printf("  C11 support: %s\n", HAVE_C11 ? "yes" : "no");
#ifdef COMPILER_GNU_LIKE
printf("  Compiler: GNU-like (gcc/clang)\n");
#elif defined(COMPILER_MSVC)
printf("  Compiler: MSVC\n");
#endif
// Version checking
printf("\nVersion: %d.%d.%d\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
if (VERSION_GE(1, 0, 0)) {
printf("  Version 1.0.0 or later\n");
}
if (VERSION_GE(1, 0, 5)) {
printf("  Version includes patch 5\n");
}
// Sleep demonstration (if supported)
printf("\nSleeping for 1 second...\n");
SLEEP(1000);
printf("Done!\n");
return 0;
}

Include Guards and Header Management

// ============================================================
// INCLUDE GUARDS (HEADER FILE)
// ============================================================
// File: mylib.h
#ifndef MYLIB_H
#define MYLIB_H
// This prevents multiple inclusions
// Macros
#define MAX_BUFFER 1024
#define DEFAULT_VALUE 42
// Function declarations
void init_library(void);
int process_data(int input);
void cleanup_library(void);
// Inline functions (C99)
static inline int square(int x) {
return x * x;
}
#endif // MYLIB_H
// ============================================================
// ALTERNATIVE: #pragma once (non-standard but widely supported)
// ============================================================
// File: mylib2.h
#pragma once
// Simpler, but not standard C
// Works on most compilers (gcc, clang, MSVC)
#define ANOTHER_MACRO 123
// ============================================================
// DEMONSTRATION
// ============================================================
// File: main.c
#include <stdio.h>
#include "mylib.h"
#include "mylib.h"  // Second inclusion does nothing due to guards
#include "mylib2.h"
// Demonstrate include guards
int main() {
printf("=== Include Guards ===\n\n");
printf("MAX_BUFFER = %d\n", MAX_BUFFER);
printf("DEFAULT_VALUE = %d\n", DEFAULT_VALUE);
printf("ANOTHER_MACRO = %d\n", ANOTHER_MACRO);
printf("square(5) = %d\n", square(5));
return 0;
}

Predefined Macros

#include <stdio.h>
// ============================================================
// PREDEFINED MACROS
// ============================================================
int main() {
printf("=== Predefined Macros ===\n\n");
// File and line information
printf("__FILE__: %s\n", __FILE__);
printf("__LINE__: %d\n", __LINE__);
printf("__func__: %s\n", __func__);
// Date and time of compilation
printf("__DATE__: %s\n", __DATE__);
printf("__TIME__: %s\n", __TIME__);
// C standard version
printf("__STDC__: %d\n", __STDC__);
#ifdef __STDC_VERSION__
printf("__STDC_VERSION__: %ld\n", __STDC_VERSION__);
#endif
// Compiler information
#ifdef __GNUC__
printf("__GNUC__: %d\n", __GNUC__);
printf("__GNUC_MINOR__: %d\n", __GNUC_MINOR__);
#endif
#ifdef __clang__
printf("__clang__: %d\n", __clang__);
printf("__clang_major__: %d\n", __clang_major__);
#endif
#ifdef _MSC_VER
printf("_MSC_VER: %d\n", _MSC_VER);
#endif
// Platform detection
#ifdef __linux__
printf("__linux__ defined\n");
#endif
#ifdef __APPLE__
printf("__APPLE__ defined\n");
#endif
#ifdef _WIN32
printf("_WIN32 defined\n");
#endif
// Endianness
#ifdef __BYTE_ORDER__
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
printf("Little-endian\n");
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
printf("Big-endian\n");
#endif
#endif
return 0;
}

Advanced Macro Techniques

#include <stdio.h>
// ============================================================
// ADVANCED MACRO TECHNIQUES
// ============================================================
// 1. X-Macros (for generating code from a list)
#define COLORS \
X(RED, "FF0000") \
X(GREEN, "00FF00") \
X(BLUE, "0000FF") \
X(BLACK, "000000") \
X(WHITE, "FFFFFF")
// Generate enum
#define X(name, hex) name,
typedef enum {
COLORS
COLOR_COUNT
} Color;
#undef X
// Generate string array
#define X(name, hex) #name,
const char *color_names[] = {
COLORS
};
#undef X
// Generate hex code array
#define X(name, hex) hex,
const char *color_hex[] = {
COLORS
};
#undef X
// Generate switch-case
#define X(name, hex) \
case name: return #name;
const char* color_to_string(Color c) {
switch (c) {
COLORS
default: return "UNKNOWN";
}
}
#undef X
// 2. Macro overloading (varying number of arguments)
#define GET_MACRO(_1, _2, _3, _4, NAME, ...) NAME
// 1 argument
#define PRINT_1(a) printf("%d\n", a)
// 2 arguments
#define PRINT_2(a, b) printf("%d %d\n", a, b)
// 3 arguments
#define PRINT_3(a, b, c) printf("%d %d %d\n", a, b, c)
#define PRINT(...) \
GET_MACRO(__VA_ARGS__, PRINT_3, PRINT_2, PRINT_1)(__VA_ARGS__)
// 3. Recursive macros (with workarounds)
#define EVAL(...)  EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL1(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL2(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
#define EVAL3(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
#define EVAL4(...) __VA_ARGS__
#define MAP_OUT
// 4. Assert macro with message
#define ASSERT(expr) \
do { \
if (!(expr)) { \
fprintf(stderr, "Assertion failed: %s at %s:%d\n", \
#expr, __FILE__, __LINE__); \
abort(); \
} \
} while (0)
// 5. Compile-time assertions (C11 _Static_assert)
#define STATIC_ASSERT(expr, msg) \
_Static_assert(expr, msg)
// 6. Logging with different levels
#define LOG_LEVEL_NONE  0
#define LOG_LEVEL_ERROR 1
#define LOG_LEVEL_WARN  2
#define LOG_LEVEL_INFO  3
#define LOG_LEVEL_DEBUG 4
#ifndef LOG_LEVEL
#define LOG_LEVEL LOG_LEVEL_INFO
#endif
#define LOG_ERROR(fmt, ...) \
do { if (LOG_LEVEL >= LOG_LEVEL_ERROR) \
fprintf(stderr, "ERROR: " fmt "\n", __VA_ARGS__); \
} while (0)
#define LOG_WARN(fmt, ...) \
do { if (LOG_LEVEL >= LOG_LEVEL_WARN) \
fprintf(stderr, "WARN: " fmt "\n", __VA_ARGS__); \
} while (0)
#define LOG_INFO(fmt, ...) \
do { if (LOG_LEVEL >= LOG_LEVEL_INFO) \
printf("INFO: " fmt "\n", __VA_ARGS__); \
} while (0)
#define LOG_DEBUG(fmt, ...) \
do { if (LOG_LEVEL >= LOG_LEVEL_DEBUG) \
printf("DEBUG: " fmt "\n", __VA_ARGS__); \
} while (0)
int main() {
printf("=== Advanced Macro Techniques ===\n\n");
// X-Macros
printf("X-Macros example:\n");
for (int i = 0; i < COLOR_COUNT; i++) {
printf("  %s = #%s\n", color_names[i], color_hex[i]);
}
printf("\ncolor_to_string(RED): %s\n", color_to_string(RED));
// Overloaded macros
printf("\nOverloaded PRINT macro:\n");
PRINT(10);
PRINT(20, 30);
PRINT(40, 50, 60);
// Assert macros
int x = 10;
ASSERT(x > 0);  // OK
// Compile-time assertion
STATIC_ASSERT(sizeof(int) >= 4, "int must be at least 4 bytes");
// Logging macros
printf("\nLogging with levels:\n");
LOG_ERROR("Division by zero at %d", __LINE__);
LOG_WARN("Low memory: %d bytes free", 1024);
LOG_INFO("Application started");
LOG_DEBUG("x = %d", x);
return 0;
}

Debugging Macros

#include <stdio.h>
#include <stdlib.h>
// ============================================================
// DEBUGGING MACROS
// ============================================================
// Trace macro - prints function entry/exit
#define TRACE() \
printf("TRACE: %s() at %s:%d\n", __func__, __FILE__, __LINE__)
#define TRACE_ENTER() \
printf("--> %s()\n", __func__)
#define TRACE_EXIT() \
printf("<-- %s()\n", __func__)
#define TRACE_EXIT_VAL(val) \
printf("<-- %s() returning %d\n", __func__, val); \
return val
// Memory debugging
#define MALLOC(size) \
malloc_with_info(size, __FILE__, __LINE__)
#define FREE(ptr) \
free_with_info(ptr, __FILE__, __LINE__)
void* malloc_with_info(size_t size, const char *file, int line) {
void *ptr = malloc(size);
printf("MALLOC: %zu bytes at %p from %s:%d\n", 
size, ptr, file, line);
return ptr;
}
void free_with_info(void *ptr, const char *file, int line) {
printf("FREE: %p from %s:%d\n", ptr, file, line);
free(ptr);
}
// Variable dump
#define DUMP_INT(x) printf(#x " = %d (at %s:%d)\n", x, __FILE__, __LINE__)
#define DUMP_PTR(x) printf(#x " = %p (at %s:%d)\n", x, __FILE__, __LINE__)
#define DUMP_STRING(x) printf(#x " = \"%s\" (at %s:%d)\n", x, __FILE__, __LINE__)
// Generic dump using _Generic
#define DUMP(x) _Generic((x), \
int: DUMP_INT, \
double: DUMP_DOUBLE, \
char*: DUMP_STRING, \
void*: DUMP_PTR \
)(x)
// Array dump
#define DUMP_ARRAY(arr, size) \
do { \
printf(#arr " (%zu elements): ", size); \
for (size_t _i = 0; _i < (size); _i++) { \
printf("%d ", arr[_i]); \
} \
printf("\n"); \
} while (0)
// Breakpoint macro (if supported)
#ifdef __GNUC__
#define BREAKPOINT() __asm__("int $3")
#elif defined(_MSC_VER)
#define BREAKPOINT() __debugbreak()
#else
#define BREAKPOINT() ((void)0)
#endif
// Example functions for tracing
int factorial(int n) {
TRACE_ENTER();
if (n <= 1) {
TRACE_EXIT_VAL(1);
}
int result = n * factorial(n - 1);
TRACE_EXIT_VAL(result);
return result;  // Won't be reached due to TRACE_EXIT_VAL
}
// Better approach for tracing
int factorial2(int n) {
TRACE_ENTER();
if (n <= 1) {
TRACE_EXIT();
return 1;
}
int result = n * factorial2(n - 1);
TRACE_EXIT();
return result;
}
int main() {
printf("=== Debugging Macros ===\n\n");
// Trace example
printf("Factorial trace:\n");
int result = factorial2(5);
printf("Result: %d\n\n", result);
// Memory debugging
printf("Memory debugging:\n");
int *p = MALLOC(10 * sizeof(int));
for (int i = 0; i < 10; i++) {
p[i] = i * i;
}
FREE(p);
// Dump macros
printf("\nDump macros:\n");
int x = 42;
double y = 3.14;
char *str = "Hello";
int arr[] = {1, 2, 3, 4, 5};
DUMP_INT(x);
DUMP_STRING(str);
DUMP_ARRAY(arr, 5);
return 0;
}

Common Pitfalls and Best Practices

#include <stdio.h>
// ============================================================
// COMMON PITFALLS
// ============================================================
// PITFALL 1: Missing parentheses
#define BAD_SQUARE(x) x * x
#define GOOD_SQUARE(x) ((x) * (x))
// PITFALL 2: Multiple evaluation of arguments
#define BAD_MAX(a, b) ((a) > (b) ? (a) : (b))
// GOOD_MAX defined above
// PITFALL 3: Side effects in macro arguments
#define INC_AND_MAX(a, b) ((a) > (b) ? (a) : (b))
// PITFALL 4: Semicolon in macro definition
#define BAD_PRINT(msg) printf("%s\n", msg);  // Don't put semicolon here
#define GOOD_PRINT(msg) printf("%s\n", msg)
// PITFALL 5: Macro without do-while for multiple statements
#define BAD_SWAP(a, b, type) \
type temp = a; \
a = b; \
b = temp
#define GOOD_SWAP(a, b, type) \
do { \
type temp = a; \
a = b; \
b = temp; \
} while (0)
// PITFALL 6: Macro names that conflict
#define max(a, b) ((a) > (b) ? (a) : (b))  // May conflict with stdlib.h
// PITFALL 7: Forgetting to undef temporary macros
#define TEMP_MACRO(x) ((x) * 2)
// ============================================================
// BEST PRACTICES
// ============================================================
// Best Practice 1: Uppercase macro names
#define MAX_BUFFER_SIZE 1024
#define FOREACH(i, n) for (int i = 0; i < (n); i++)
// Best Practice 2: Parenthesize everything
#define MULTIPLY(a, b) ((a) * (b))
// Best Practice 3: Use do-while for multi-statement macros
#define SAFE_SWAP(a, b, type) \
do { \
type temp = a; \
a = b; \
b = temp; \
} while (0)
// Best Practice 4: Avoid side effects in macro arguments
#define SAFE_MAX(a, b) \
({ __typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
_a > _b ? _a : _b; })
// Best Practice 5: Use inline functions when possible
static inline int max_inline(int a, int b) {
return a > b ? a : b;
}
// Best Practice 6: Document macros clearly
/**
* Compute the square of a number
* @param x The number to square
* @return x * x
*/
#define SQUARE(x) ((x) * (x))
// Best Practice 7: Use #undef for temporary macros
#define CREATE_LIST(name, size) \
int name[size]; \
for (int i = 0; i < size; i++) name[i] = 0
// Later, undefine if no longer needed
#undef CREATE_LIST
int main() {
printf("=== Common Pitfalls and Best Practices ===\n\n");
// Pitfall 1 demonstration
printf("Pitfall 1 - Missing parentheses:\n");
printf("  BAD_SQUARE(3+2) = %d (expected 25)\n", BAD_SQUARE(3+2));
printf("  GOOD_SQUARE(3+2) = %d\n\n", GOOD_SQUARE(3+2));
// Pitfall 3 demonstration
printf("Pitfall 3 - Side effects:\n");
int a = 5, b = 3;
int result = INC_AND_MAX(a++, b);
printf("  a was incremented twice! a=%d, result=%d\n\n", a, result);
// Pitfall 4 demonstration
printf("Pitfall 4 - Semicolon issues:\n");
if (1)
BAD_PRINT("This prints");
printf("But this always prints because of semicolon!\n\n");
// Pitfall 5 demonstration
printf("Pitfall 5 - Multi-statement macros:\n");
int x = 10, y = 20;
printf("  Before BAD_SWAP: x=%d, y=%d\n", x, y);
if (x > 0)
BAD_SWAP(x, y, int);  // Only first statement executes in if
printf("  After BAD_SWAP:  x=%d, y=%d (didn't swap properly)\n", x, y);
x = 10, y = 20;
printf("  Before GOOD_SWAP: x=%d, y=%d\n", x, y);
if (x > 0)
GOOD_SWAP(x, y, int);
printf("  After GOOD_SWAP:  x=%d, y=%d\n\n", x, y);
// Best Practice: Use inline functions when possible
printf("Best Practice - Inline functions:\n");
printf("  max_inline(10, 20) = %d\n", max_inline(10, 20));
return 0;
}

Macro vs Inline Functions Comparison

#include <stdio.h>
#include <time.h>
// ============================================================
// MACRO VS INLINE FUNCTION COMPARISON
// ============================================================
// Macro version
#define MAX_MACRO(a, b) ((a) > (b) ? (a) : (b))
// Inline function version
static inline int max_inline(int a, int b) {
return a > b ? a : b;
}
// Generic inline (C11)
static inline double max_double(double a, double b) {
return a > b ? a : b;
}
// Macro with type safety issues
#define ADD_MACRO(a, b) ((a) + (b))
// Type-safe inline
static inline int add_int(int a, int b) { return a + b; }
static inline double add_double(double a, double b) { return a + b; }
// Performance comparison
void performance_test() {
const int ITERATIONS = 100000000;
int a = 5, b = 10;
int result = 0;
clock_t start, end;
printf("=== Performance: Macro vs Inline ===\n\n");
// Macro
start = clock();
for (int i = 0; i < ITERATIONS; i++) {
result += MAX_MACRO(a, b);
}
end = clock();
double macro_time = (double)(end - start) / CLOCKS_PER_SEC;
// Inline
start = clock();
for (int i = 0; i < ITERATIONS; i++) {
result += max_inline(a, b);
}
end = clock();
double inline_time = (double)(end - start) / CLOCKS_PER_SEC;
printf("Macro time:   %.3f seconds\n", macro_time);
printf("Inline time:  %.3f seconds\n", inline_time);
printf("Difference:   %.2f%%\n", 
(macro_time - inline_time) / macro_time * 100);
printf("Result (prevents optimization): %d\n\n", result);
}
int main() {
printf("=== Macro vs Inline Function Comparison ===\n\n");
// Type safety
printf("Type safety:\n");
printf("  MAX_MACRO(5, 10) = %d\n", MAX_MACRO(5, 10));
printf("  MAX_MACRO(5.5, 10.5) = %d (type conversion!)\n", 
MAX_MACRO(5.5, 10.5));
printf("  max_inline(5, 10) = %d\n", max_inline(5, 10));
// printf("  max_inline(5.5, 10.5) = %d\n", max_inline(5.5, 10.5)); // Error
printf("  max_double(5.5, 10.5) = %.1f\n\n", max_double(5.5, 10.5));
// Debugging
printf("Debugging:\n");
printf("  Macros are harder to debug (no step into)\n");
printf("  Inline functions work with debugger\n\n");
// Code size
printf("Code size:\n");
printf("  Macros may cause code bloat if used many times\n");
printf("  Inline functions similar, but compiler decides\n\n");
// When to use each:
printf("When to use macros:\n");
printf("  - Simple constant definitions\n");
printf("  - Conditional compilation\n");
printf("  - Code generation (X-macros)\n");
printf("  - When you need __FILE__, __LINE__\n\n");
printf("When to use inline functions:\n");
printf("  - Type-safe operations\n");
printf("  - Complex logic\n");
printf("  - Debugging support\n");
printf("  - When performance matters less than safety\n");
performance_test();
return 0;
}

Summary Table

DirectivePurposeExample
#defineDefine macro#define PI 3.14159
#undefRemove macro#undef PI
#ifConditional#if DEBUG
#ifdefIf defined#ifdef __linux__
#ifndefIf not defined#ifndef HEADER_H
#elseElse clause#else
#elifElse if#elif defined(__APPLE__)
#endifEnd conditional#endif
#includeInclude file#include <stdio.h>
#errorError message#error "Unsupported platform"
#pragmaCompiler-specific#pragma once
#lineLine control#line 100 "newfile.c"
#Stringify#x becomes "x"
##Token pastea##b becomes ab

Conclusion

Preprocessor macros are a powerful feature in C that enable code generation, conditional compilation, and platform adaptation. Key takeaways:

  1. Object-like macros provide named constants and simple substitutions
  2. Function-like macros can inline code but require careful parentheses
  3. Conditional compilation adapts code to different platforms and configurations
  4. Stringification and token pasting enable advanced code generation techniques
  5. X-macros provide a way to generate repetitive code from a single list
  6. Include guards prevent multiple inclusion of header files

Best practices:

  • Always parenthesize macro parameters and the entire macro body
  • Use uppercase names for macros to distinguish them from functions
  • Avoid side effects in macro arguments
  • Use do { ... } while (0) for multi-statement macros
  • Consider inline functions instead of macros for type-safe operations
  • Document macros clearly, especially complex ones
  • #undef temporary macros after use

Common pitfalls to avoid:

  • Missing parentheses leading to operator precedence issues
  • Multiple evaluation of arguments with side effects
  • Semicolons in macro definitions
  • Macro name conflicts with standard library
  • Not handling multi-statement macros correctly in conditionals

Mastering preprocessor macros elevates C programming from simple code writing to sophisticated code generation, enabling solutions that are portable, efficient, and maintainable across different platforms and configurations.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper