The C preprocessor is a powerful code transformation tool that runs before the compiler sees your code. Think of it as a find-and-replace engine on steroids: it can substitute text, include files, conditionally remove code blocks, and even generate repetitive code. The preprocessor knows nothing about C syntax, types, or semantics — it operates purely on text tokens.This textual nature is both its power and its danger. Used well, the preprocessor eliminates boilerplate and enables clean cross-platform code. Used carelessly, it creates debugging nightmares where the code you read is not the code the compiler sees. The key discipline: keep macros simple, document them well, and prefer inline functions whenever possible.
// header.h - Traditional guard#ifndef HEADER_H#define HEADER_H// Header contents...#endif // HEADER_H// Modern alternative (non-standard but widely supported)#pragma once// Contents...
// Simple constants#define MAX_SIZE 1024#define PI 3.14159265358979// Multi-line (use sparingly)#define LONG_STRING "This is a very long string that " \ "spans multiple lines"// Empty macro (for conditional compilation markers)#define DEPRECATED// Undef to remove#undef MAX_SIZE
#include <stdio.h>// ## concatenates tokens#define CONCAT(a, b) a##b#define MAKE_VAR(n) var_##nint main(void) { int MAKE_VAR(1) = 10; // Creates: int var_1 = 10; int MAKE_VAR(2) = 20; // Creates: int var_2 = 20; printf("%d %d\n", var_1, var_2); return 0;}// Generating function names#define DEFINE_GETTER(type, name) \ type get_##name(void) { return name; }static int value = 42;DEFINE_GETTER(int, value) // Creates: int get_value(void) { return value; }// Generic min/max for different types#define DEFINE_MINMAX(type) \ static inline type type##_min(type a, type b) { return a < b ? a : b; } \ static inline type type##_max(type a, type b) { return a > b ? a : b; }DEFINE_MINMAX(int)DEFINE_MINMAX(double)// Creates: int_min, int_max, double_min, double_max
A powerful technique for maintaining parallel data structures that must stay in sync. The problem X-macros solve: you have an enum of error codes, a table of error strings, and maybe a table of error names. Without X-macros, every time you add a new error code you must update three different places — and if you forget one, you get a subtle runtime bug. X-macros let you define the data once and mechanically generate all the parallel structures from it.This pattern is used extensively in real-world C codebases including the Linux kernel, SQLite, and Redis.
This macro is the backbone of Linux kernel data structures. The idea: instead of embedding a pointer to your data inside a list node, you embed a list node inside your data. Then, given a pointer to the list node, you calculate backwards to find the start of the containing structure. It is like finding a book’s title page by knowing which page the table of contents is on and how many pages from the front it starts.
#include <stddef.h>// Get pointer to containing structure from member pointer.// The math: subtract the member's offset within the struct from the member's// actual address. The result is the address of the struct itself.#define container_of(ptr, type, member) \ ((type *)((char *)(ptr) - offsetof(type, member)))typedef struct { int data;} ListNode;typedef struct { int id; char name[32]; ListNode node; // Embedded list node} Entry;void process_node(ListNode *n) { // Get the Entry containing this node Entry *e = container_of(n, Entry, node); printf("Entry id: %d, name: %s\n", e->id, e->name);}