C Preprocessor Macros Cheat Sheet
Details the C preprocessor: object-like and function-like macros, conditional compilation, include guards, and stringizing and token-pasting operators.
Object-like and Function-like Macros
Text substitution performed before compilation.
#define PI 3.14159265358979 // Object-like macro (constant)#define MAX(a, b) ((a) > (b) ? (a) : (b)) // Function-like macro#define SQUARE(x) ((x) * (x)) // Always parenthesize params and resultdouble area = PI * SQUARE(5); // Expands safely thanks to the parensint m = MAX(3, 7); // Expands to ((3) > (7) ? (3) : (7))
Conditional Compilation
Include or exclude code based on compile-time conditions.
#define DEBUG 1#if DEBUG #define LOG(msg) printf("[DEBUG] %s\n", msg)#else #define LOG(msg)#endif#ifndef HEADER_H#define HEADER_H// header contents go here (include guard)#endif#ifdef _WIN32 // Windows-specific code#elif defined(__linux__) // Linux-specific code#endif
Stringizing and Token Pasting
The # and ## preprocessor operators.
#define STRINGIFY(x) #x // Turns an argument into a string literal#define CONCAT(a, b) a##b // Pastes two tokens togetherprintf("%s\n", STRINGIFY(hello)); // Prints: helloint CONCAT(var, 1) = 10; // Expands to: int var1 = 10;#define VERSION_MAJOR 1#define VERSION_MINOR 2// Adjacent string literals concatenate at compile timeconst char *ver = STRINGIFY(VERSION_MAJOR) "." STRINGIFY(VERSION_MINOR);
Pitfalls & Good Practices
Rules of thumb for writing safe macros.
- Parenthesize everything- Wrap macro parameters and the whole expansion in parens to avoid operator-precedence bugs
- No side effects- MAX(i++, j) evaluates an argument twice — avoid expressions with side effects in macro args
- Macros vs functions- Macros are text substitution at compile time: no type checking and no function call overhead
- Include guards- #ifndef / #define / #endif prevents a header from being included more than once per translation unit
- #pragma once- A non-standard but widely supported alternative to manual include guards
- Line continuation- A trailing backslash (\) continues a #define across multiple lines
- Variadic macros- #define LOG(fmt, ...) printf(fmt, __VA_ARGS__) accepts a variable argument list (C99)
The X-Macro Pattern
Generate repetitive code (enums, string tables, switch cases) from a single list of data.
#define COLOR_LIST \ X(RED) \ X(GREEN) \ X(BLUE)#define X(name) name,enum Color { COLOR_LIST }; // expands to: RED, GREEN, BLUE,#undef X#define X(name) #name,const char *color_names[] = { COLOR_LIST }; // expands to string literals#undef X// Adding a color only requires editing COLOR_LIST once — the enum// and the name table stay in sync automatically.
Variadic Macros
__VA_ARGS__ and the GNU trick for handling a trailing comma with zero extra arguments.
#define LOG(fmt, ...) fprintf(stderr, "[LOG] " fmt "\n", __VA_ARGS__)// GNU extension: swallows the comma when no varargs are passed#define LOG_SAFE(fmt, ...) fprintf(stderr, "[LOG] " fmt "\n", ##__VA_ARGS__)// C++20 / C23 standard alternative: __VA_OPT__#define LOG_STD(fmt, ...) fprintf(stderr, "[LOG] " fmt "\n" __VA_OPT__(,) __VA_ARGS__)LOG("value=%d", 42); // fine, has an extra argumentLOG_SAFE("startup"); // fine even with zero varargs
Predefined Diagnostic Macros
Compiler-supplied macros for logging, assertions, and version checks.
#include <assert.h>#define TRACE() \ fprintf(stderr, "%s:%d in %s()\n", __FILE__, __LINE__, __func__)void process(int n) { TRACE(); static_assert(sizeof(int) >= 4, "int must be at least 32 bits"); // C11 assert(n >= 0 && "n must be non-negative"); // runtime check, disabled by NDEBUG}#if __STDC_VERSION__ >= 201112L // C11 or later specific code path#endif
Type-Generic Macros with _Generic
C11's _Generic lets a single macro dispatch to a type-appropriate function at compile time.
#include <math.h>#define ABS(x) _Generic((x), \ int: abs, \ long: labs, \ float: fabsf, \ double: fabs \)(x)int a = ABS(-5); // expands to abs(-5)double b = ABS(-3.14); // expands to fabs(-3.14)// _Generic selects the matching association based on the argument's// type, giving type-safe 'overloading' without C++ templates.
Lesser-Known Directives & Macros
Preprocessor directives and identifiers beyond the everyday #define/#if set.
- #error "msg"- Aborts compilation immediately with a custom diagnostic message
- #warning "msg"- (non-standard but widely supported) emits a warning without stopping the build
- #undef NAME- Removes a macro definition so it can be safely redefined
- #line N "file"- Overrides the reported line number/filename, used by code generators
- __has_include(<hdr>)- (C23/GNU/Clang) tests header availability inside #if for portable optional includes
- __STDC__ / __STDC_VERSION__- Identify a conforming C compiler and which C standard revision it targets
- Two-level token pasting- #define CAT(a,b) CAT_(a,b) / #define CAT_(a,b) a##b forces macro args to expand before pasting
Wrap multi-statement macros in a do { ... } while(0) block so they act like a single statement and don't silently break when used inside an unbraced if/else.