C — Preprocessor
The C preprocessor is a text-processing tool that runs before the actual compiler. It transforms your source code by performing text substitutions, file inclusion, and conditional compilation. The preprocessor reads your .c files and produces a "translation unit" with all directives resolved. Every line beginning with # is a preprocessor directive.
| 1 | // Preprocessor directives are processed BEFORE compilation. |
| 2 | // The compiler never sees them — they are resolved into pure C. |
| 3 | |
| 4 | #include <stdio.h> // Include a system header |
| 5 | #define MAX_SIZE 1024 // Define a macro constant |
| 6 | |
| 7 | int main(void) { |
| 8 | int arr[MAX_SIZE]; // Preprocessor replaces MAX_SIZE with 1024 |
| 9 | printf("Array size: %d\n", MAX_SIZE); |
| 10 | return 0; |
| 11 | } |
| 12 | |
| 13 | // After preprocessing, the compiler sees: |
| 14 | // #include <stdio.h> → contents of stdio.h pasted here |
| 15 | // MAX_SIZE → replaced with 1024 everywhere |
You can inspect the preprocessor output using gcc -E. This is invaluable for debugging macro issues.
| 1 | # See preprocessor output (stops after preprocessing) |
| 2 | gcc -E main.c -o main.i |
| 3 | |
| 4 | # With line markers for debugging |
| 5 | gcc -E -C main.c -o main.i |
| 6 | |
| 7 | # Common flag combinations |
| 8 | gcc -E -dM main.c # Dump all macro definitions |
#include copies the contents of another file into the current file. There are two forms: angle brackets for system headers and double quotes for project headers.
| 1 | // Angle brackets: search system include paths first |
| 2 | #include <stdio.h> // /usr/include/stdio.h |
| 3 | #include <stdint.h> // /usr/include/stdint.h |
| 4 | #include <math.h> // /usr/include/math.h |
| 5 | |
| 6 | // Double quotes: search current directory first, then system paths |
| 7 | #include "myheader.h" // ./myheader.h first |
| 8 | #include "utils/log.h" // ./utils/log.h first |
| 9 | |
| 10 | // The include path search order: |
| 11 | // 1. Directory of the file containing #include (for quotes only) |
| 12 | // 2. Directories specified with -I flag |
| 13 | // 3. System default include paths (/usr/include, etc.) |
info
Object-like macros replace an identifier with a value. They are simple text substitutions with no type safety.
| 1 | // Simple constant macros |
| 2 | #define PI 3.14159265358979 |
| 3 | #define MAX_BUFFER 4096 |
| 4 | #define VERSION "2.1.0" |
| 5 | #define NEWLINE '\n' |
| 6 | |
| 7 | // Multi-token macros |
| 8 | #define ARRAY_SIZE 100 |
| 9 | #define COMMA , |
| 10 | |
| 11 | // Empty macro (defines the symbol but replaces with nothing) |
| 12 | #define DEBUG_MODE |
| 13 | |
| 14 | // Negative number macros |
| 15 | #define MIN_INT (-2147483647 - 1) |
| 16 | |
| 17 | // Hex and octal |
| 18 | #define COLOR_RED 0xFF0000 |
| 19 | #define PERM_READ 0444 |
Function-like macros accept arguments and perform text substitution on each invocation. They look like function calls but have no type checking.
| 1 | // Basic function-like macro |
| 2 | #define SQUARE(x) ((x) * (x)) |
| 3 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) |
| 4 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) |
| 5 | #define ABS(x) ((x) < 0 ? -(x) : (x)) |
| 6 | |
| 7 | // Multi-parameter macros |
| 8 | #define CLAMP(val, lo, hi) ((val) < (lo) ? (lo) : ((val) > (hi) ? (hi) : (val))) |
| 9 | #define SWAP(a, b) do { \n typeof(a) _tmp = (a); \n (a) = (b); \n (b) = _tmp; \n} while(0) |
| 10 | |
| 11 | // Macros with no parameters (still need empty parens to invoke) |
| 12 | #define DEBUG_ENABLED() (1) |
| 13 | |
| 14 | #include <stdio.h> |
| 15 | |
| 16 | int main(void) { |
| 17 | int a = 5, b = 3; |
| 18 | printf("Max: %d\n", MAX(a, b)); // Max: 5 |
| 19 | printf("Square: %d\n", SQUARE(4)); // Square: 16 |
| 20 | printf("Clamped: %d\n", CLAMP(15, 0, 10)); // Clamped: 10 |
| 21 | return 0; |
| 22 | } |
Macros are textual replacements, not expressions. Without careful parenthesization, operator precedence causes subtle bugs. Always wrap macro arguments and the entire macro body in parentheses.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | // BAD: no parenthesization — operator precedence causes bugs |
| 4 | #define BAD_SQUARE(x) x * x |
| 5 | #define BAD_MAX(a, b) a > b ? a : b |
| 6 | |
| 7 | // GOOD: fully parenthesized |
| 8 | #define GOOD_SQUARE(x) ((x) * (x)) |
| 9 | #define GOOD_MAX(a, b) ((a) > (b) ? (a) : (b)) |
| 10 | |
| 11 | int main(void) { |
| 12 | // Bug demonstration |
| 13 | printf("BAD_SQUARE(3+1) = %d\n", BAD_SQUARE(3+1)); |
| 14 | // Expands to: 3+1 * 3+1 = 3 + 3 + 1 = 7 (WRONG!) |
| 15 | |
| 16 | printf("GOOD_SQUARE(3+1) = %d\n", GOOD_SQUARE(3+1)); |
| 17 | // Expands to: ((3+1) * (3+1)) = 16 (CORRECT) |
| 18 | |
| 19 | // Side effect bug: macro evaluates argument multiple times |
| 20 | int x = 0; |
| 21 | printf("BAD_MAX(x++, 5) = %d\n", BAD_MAX(x++, 5)); |
| 22 | // Expands to: x++ > 5 ? x++ : 5 — x incremented TWICE |
| 23 | |
| 24 | x = 0; |
| 25 | printf("GOOD_MAX(x++, 5) = %d\n", GOOD_MAX(x++, 5)); |
| 26 | // Same problem! x still incremented twice. |
| 27 | // Only storing in a local variable (do-while trick) prevents this. |
| 28 | |
| 29 | return 0; |
| 30 | } |
warning
When a macro expands to multiple statements, wrapping it in do { ... } while(0) makes it behave as a single statement. This prevents bugs when the macro is used inside unbraced if/else blocks.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | // WRONG: multi-statement macro without do-while |
| 4 | #define BAD_LOG(msg) printf("LOG: "); printf("%s\n", msg) |
| 5 | |
| 6 | // CORRECT: do-while(0) wrapping |
| 7 | #define LOG(msg) do { \ |
| 8 | printf("[LOG] %s:%d: ", __FILE__, __LINE__); \ |
| 9 | printf("%s\n", msg); \ |
| 10 | } while(0) |
| 11 | |
| 12 | // Another correct pattern: comma operator (single expression) |
| 13 | #define LOG2(msg) (printf("[LOG] "), printf("%s\n", msg)) |
| 14 | |
| 15 | int main(void) { |
| 16 | int flag = 1; |
| 17 | |
| 18 | // BAD macro: this breaks with unbraced if |
| 19 | // if (flag) |
| 20 | // BAD_LOG("hello"); // Expands to TWO statements — only first is conditional! |
| 21 | // BAD_LOG("world"); // This always runs! |
| 22 | |
| 23 | // GOOD macro: works correctly |
| 24 | if (flag) |
| 25 | LOG("hello"); // do-while makes this a single statement |
| 26 | else |
| 27 | LOG("nothing"); |
| 28 | |
| 29 | return 0; |
| 30 | } |
#undef removes a previously defined macro. After #undef, the identifier is no longer defined as a macro. This is useful for redefining macros or limiting macro scope.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | #define LOG_LEVEL 2 |
| 4 | |
| 5 | void debug_function(void) { |
| 6 | #undef LOG_LEVEL |
| 7 | #define LOG_LEVEL 0 // Override for this section only |
| 8 | printf("Debug level: %d\n", LOG_LEVEL); |
| 9 | } |
| 10 | |
| 11 | int main(void) { |
| 12 | printf("Main level: %d\n", LOG_LEVEL); // 2 |
| 13 | |
| 14 | // Redefine a macro |
| 15 | #undef LOG_LEVEL |
| 16 | #define LOG_LEVEL 3 |
| 17 | printf("New level: %d\n", LOG_LEVEL); // 3 |
| 18 | |
| 19 | // #undef on undefined macro is harmless (no error) |
| 20 | #undef NONEXISTENT_MACRO |
| 21 | |
| 22 | return 0; |
| 23 | } |
The # operator converts a macro argument into a string literal. This is called "stringification" and is extremely useful for debugging and logging.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | // Stringification: # turns the argument into a string |
| 4 | #define PRINT_VAR(x) printf(#x " = %d\n", (x)) |
| 5 | #define PRINT_EXPR(x) printf(#x " = %d\n", (x)) |
| 6 | |
| 7 | // Debug macro that shows variable name and value |
| 8 | #define DEBUG_PRINT(var) \ |
| 9 | fprintf(stderr, "[DEBUG] %s:%d: %s = %d\n", \ |
| 10 | __FILE__, __LINE__, #var, (var)) |
| 11 | |
| 12 | // Works with expressions too |
| 13 | #define LOG_COMPARE(a, op, b) \ |
| 14 | printf(#a " " #op " " #b " is %s\n", \ |
| 15 | ((a) op (b)) ? "true" : "false") |
| 16 | |
| 17 | int main(void) { |
| 18 | int count = 42; |
| 19 | int x = 10, y = 20; |
| 20 | |
| 21 | PRINT_VAR(count); // prints: count = 42 |
| 22 | PRINT_VAR(x + y); // prints: x + y = 30 |
| 23 | |
| 24 | DEBUG_PRINT(count); // shows file, line, name, and value |
| 25 | |
| 26 | LOG_COMPARE(x, <, y); // prints: x < y is true |
| 27 | LOG_COMPARE(x, >, y); // prints: x > y is false |
| 28 | |
| 29 | return 0; |
| 30 | } |
The ## operator concatenates two tokens into a single token at preprocessing time. This is called "token pasting" and enables generating identifiers dynamically.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | // Token pasting: ## joins two tokens into one |
| 4 | #define MAKE_PAIR(type, name) \ |
| 5 | type name##_x; \ |
| 6 | type name##_y |
| 7 | |
| 8 | // Create getter/setter pairs |
| 9 | #define DEFINE_ACCESSOR(type, name) \ |
| 10 | static type _##name; \ |
| 11 | type get_##name(void) { return _##name; } \ |
| 12 | void set_##name(type val) { _##name = val; } |
| 13 | |
| 14 | // Generate test functions |
| 15 | #define DECLARE_TEST(name) void test_##name(void) |
| 16 | |
| 17 | DECLARE_TEST(initialize); // Expands to: void test_initialize(void) |
| 18 | DECLARE_TEST(cleanup); // Expands to: void test_cleanup(void) |
| 19 | DECLARE_TEST(validation); // Expands to: void test_validation(void) |
| 20 | |
| 21 | // Registry of values |
| 22 | #define REG_ENTRY(id, val) { id, val } |
| 23 | |
| 24 | struct entry { |
| 25 | int id; |
| 26 | int value; |
| 27 | }; |
| 28 | |
| 29 | struct entry table[] = { |
| 30 | REG_ENTRY(1, 100), |
| 31 | REG_ENTRY(2, 200), |
| 32 | REG_ENTRY(3, 300), |
| 33 | }; |
| 34 | |
| 35 | DEFINE_ACCESSOR(int, counter); |
| 36 | DEFINE_ACCESSOR(float, ratio); |
| 37 | |
| 38 | int main(void) { |
| 39 | set_counter(42); |
| 40 | printf("counter = %d\n", get_counter()); |
| 41 | return 0; |
| 42 | } |
warning
##. The operator requires the ## to directly touch both tokens.Variadic macros accept a variable number of arguments using the __VA_ARGS__ preprocessor identifier. The ellipsis ... in the parameter list captures all trailing arguments.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | // Basic variadic macro |
| 4 | #define LOG(fmt, ...) printf("[LOG] " fmt "\n", ##__VA_ARGS__) |
| 5 | |
| 6 | // Debug logging with file/line info |
| 7 | #define DBG(fmt, ...) \ |
| 8 | fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__) |
| 9 | |
| 10 | // Count arguments (up to 10) |
| 11 | #define COUNT_ARGS(...) \ |
| 12 | COUNT_ARGS_IMPL(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) |
| 13 | #define COUNT_ARGS_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N |
| 14 | |
| 15 | // Format a matrix row |
| 16 | #define PRINT_ROW(fmt, ...) \ |
| 17 | do { \ |
| 18 | printf("[ "); \ |
| 19 | printf(fmt, __VA_ARGS__); \ |
| 20 | printf(" ]\n"); \ |
| 21 | } while(0) |
| 22 | |
| 23 | // The ## before __VA_ARGS__ swallows the comma when no args are given (GCC extension) |
| 24 | #define SAFE_LOG(fmt, ...) printf(fmt "\n", ##__VA_ARGS__) |
| 25 | |
| 26 | int main(void) { |
| 27 | LOG("System started"); |
| 28 | LOG("User %s logged in from %s", "admin", "192.168.1.1"); |
| 29 | DBG("Memory usage: %d KB", 1024); |
| 30 | |
| 31 | PRINT_ROW("%d, %d, %d", 1, 2, 3); |
| 32 | PRINT_ROW("%d, %d, %d", 4, 5, 6); |
| 33 | |
| 34 | SAFE_LOG("No extra args needed"); |
| 35 | SAFE_LOG("Value is %d", 42); |
| 36 | |
| 37 | return 0; |
| 38 | } |
The C standard defines several macros that the compiler provides automatically. These are invaluable for debugging, logging, and platform detection.
| Macro | Description | Example Value |
|---|---|---|
__FILE__ | Current source file name | "main.c" |
__LINE__ | Current line number | 42 |
__func__ | Current function name (C99) | "main" |
__DATE__ | Compilation date | "Jul 13 2026" |
__TIME__ | Compilation time | "14:30:00" |
__STDC__ | Conforms to ISO C standard | 1 |
__STDC_VERSION__ | C standard version (C99+) | 201112L |
__cplusplus | Defined when compiled as C++ | 199711L |
| 1 | #include <stdio.h> |
| 2 | |
| 3 | // Comprehensive debug macro using predefined macros |
| 4 | #define DEBUG_LOG(msg) \ |
| 5 | fprintf(stderr, "[%s %s] %s:%d in %s(): %s\n", \ |
| 6 | __DATE__, __TIME__, __FILE__, __LINE__, __func__, msg) |
| 7 | |
| 8 | // Build info struct |
| 9 | typedef struct { |
| 10 | const char *file; |
| 11 | const char *date; |
| 12 | const char *time; |
| 13 | int std_version; |
| 14 | } BuildInfo; |
| 15 | |
| 16 | BuildInfo get_build_info(void) { |
| 17 | return (BuildInfo){ |
| 18 | .file = __FILE__, |
| 19 | .date = __DATE__, |
| 20 | .time = __TIME__, |
| 21 | .std_version = (int)__STDC_VERSION__ |
| 22 | }; |
| 23 | } |
| 24 | |
| 25 | int main(void) { |
| 26 | DEBUG_LOG("Program started"); |
| 27 | |
| 28 | BuildInfo info = get_build_info(); |
| 29 | printf("Built on %s at %s\n", info.date, info.time); |
| 30 | printf("Source: %s\n", info.file); |
| 31 | printf("C Standard: %ld\n", (long)info.std_version); |
| 32 | |
| 33 | return 0; |
| 34 | } |
Conditional compilation lets you include or exclude blocks of code based on macro definitions. This enables platform-specific code, feature toggles, and debug builds.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | #define PLATFORM_WINDOWS 1 |
| 4 | // #define PLATFORM_LINUX 1 |
| 5 | // #define PLATFORM_MAC 1 |
| 6 | |
| 7 | #define FEATURE_ADVANCED 1 |
| 8 | // #define FEATURE_BETA 1 |
| 9 | |
| 10 | int main(void) { |
| 11 | |
| 12 | // #ifdef / #ifndef: check if macro is defined |
| 13 | #ifdef PLATFORM_WINDOWS |
| 14 | printf("Running on Windows\n"); |
| 15 | #elif defined(PLATFORM_LINUX) |
| 16 | printf("Running on Linux\n"); |
| 17 | #elif defined(PLATFORM_MAC) |
| 18 | printf("Running on macOS\n"); |
| 19 | #else |
| 20 | printf("Unknown platform\n"); |
| 21 | #endif |
| 22 | |
| 23 | // #if defined() — more flexible than #ifdef |
| 24 | #if defined(FEATURE_ADVANCED) && !defined(FEATURE_BETA) |
| 25 | printf("Advanced features enabled\n"); |
| 26 | #endif |
| 27 | |
| 28 | // #if with integer constant expressions |
| 29 | #define VERSION_MAJOR 2 |
| 30 | #define VERSION_MINOR 5 |
| 31 | |
| 32 | #if VERSION_MAJOR >= 2 |
| 33 | printf("Version 2+ API\n"); |
| 34 | #elif VERSION_MAJOR == 1 |
| 35 | printf("Version 1 API (legacy)\n"); |
| 36 | #endif |
| 37 | |
| 38 | // Nested conditionals |
| 39 | #ifdef FEATURE_ADVANCED |
| 40 | printf("Advanced mode\n"); |
| 41 | #ifdef FEATURE_BETA |
| 42 | printf(" Beta features active\n"); |
| 43 | #else |
| 44 | printf(" Stable features only\n"); |
| 45 | #endif |
| 46 | #endif |
| 47 | |
| 48 | return 0; |
| 49 | } |
note
#if defined(X) is generally preferred over #ifdef X because it supports logical operators like !, &&, and || more naturally.Include guards prevent a header file from being included multiple times in the same translation unit, which would cause duplicate definition errors. Every header file should have include guards.
| 1 | // myheader.h — Proper include guard pattern |
| 2 | #ifndef MYHEADER_H |
| 3 | #define MYHEADER_H |
| 4 | |
| 5 | #include <stdint.h> |
| 6 | |
| 7 | // Type definitions |
| 8 | typedef struct { |
| 9 | int32_t x; |
| 10 | int32_t y; |
| 11 | int32_t z; |
| 12 | } Point3D; |
| 13 | |
| 14 | // Function declarations |
| 15 | Point3D point_create(int32_t x, int32_t y, int32_t z); |
| 16 | float point_distance(const Point3D *a, const Point3D *b); |
| 17 | |
| 18 | // Constants |
| 19 | #define POINT_ORIGIN ((Point3D){0, 0, 0}) |
| 20 | |
| 21 | #endif /* MYHEADER_H */ |
| 1 | // Alternative: #pragma once (non-standard but widely supported) |
| 2 | #pragma once |
| 3 | |
| 4 | #include <stdint.h> |
| 5 | |
| 6 | typedef struct { |
| 7 | int32_t x; |
| 8 | int32_t y; |
| 9 | int32_t z; |
| 10 | } Point3D; |
| 11 | |
| 12 | Point3D point_create(int32_t x, int32_t y, int32_t z); |
best practice
#ifndef / #define / #endif) for maximum portability. #pragma once is supported by GCC, Clang, and MSVC but is not part of the C standard.#pragma provides implementation-specific directives to the compiler. The standard guarantees that unknown pragmas are silently ignored.
| 1 | // Common #pragma usage |
| 2 | |
| 3 | #pragma once // Include guard (non-standard, widely supported) |
| 4 | |
| 5 | #pragma pack(1) // Set struct packing alignment to 1 byte |
| 6 | typedef struct { |
| 7 | char type; // 1 byte |
| 8 | int32_t value; // 4 bytes (not padded!) |
| 9 | char flag; // 1 byte |
| 10 | } __attribute__((packed)) PackedStruct; // Total: 6 bytes |
| 11 | |
| 12 | #pragma pack() // Restore default packing |
| 13 | |
| 14 | #pragma GCC diagnostic push |
| 15 | #pragma GCC diagnostic ignored "-Wunused-parameter" |
| 16 | void unused_param(int x) { |
| 17 | // No warning here |
| 18 | } |
| 19 | #pragma GCC diagnostic pop // Restore previous diagnostics |
| 20 | |
| 21 | // #pragma omp for OpenMP parallel programming |
| 22 | // #pragma GCC poison — prevent use of dangerous functions |
| 23 | #pragma GCC poison gets // gets() is banned — causes UB |
| 24 | |
| 25 | // _Pragma operator: stringified pragma (C99) |
| 26 | _Pragma("GCC diagnostic push") |
| 27 | _Pragma("GCC diagnostic ignored \"-Wformat\"") |
| 28 | int main(void) { |
| 29 | return 0; |
| 30 | } |
#error halts compilation with an error message.#warning emits a warning (C23 standard, widely supported as extension). Both are essential for build-time validation.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | // Require a specific C standard |
| 4 | #if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 201112L |
| 5 | #error "This code requires C11 or later" |
| 6 | #endif |
| 7 | |
| 8 | // Platform validation |
| 9 | #if defined(_WIN32) && defined(__linux__) |
| 10 | #error "Cannot target both Windows and Linux" |
| 11 | #endif |
| 12 | |
| 13 | // Feature requirement |
| 14 | #if !defined(__GNUC__) && !defined(__clang__) |
| 15 | #warning "GCC or Clang recommended for best support" |
| 16 | #endif |
| 17 | |
| 18 | // Version check |
| 19 | #define MIN_VERSION 3 |
| 20 | #define REQUIRED_VERSION 2 |
| 21 | |
| 22 | #if MIN_VERSION < REQUIRED_VERSION |
| 23 | #error "Minimum version is below required version" |
| 24 | #endif |
| 25 | |
| 26 | // Compile-time assertion (C11 _Static_assert is better, but this works) |
| 27 | #define CT_ASSERT(cond) \ |
| 28 | do { \ |
| 29 | typedef char ct_assert_##__LINE__[(cond) ? 1 : -1]; \ |
| 30 | } while(0) |
| 31 | |
| 32 | int main(void) { |
| 33 | printf("All compile-time checks passed\n"); |
| 34 | return 0; |
| 35 | } |
These patterns appear frequently in production C codebases. Master them to write robust, portable software.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | // --- Debug Logging Pattern --- |
| 4 | #ifdef NDEBUG |
| 5 | #define DBG_LOG(fmt, ...) ((void)0) |
| 6 | #define DBG_ASSERT(cond) ((void)0) |
| 7 | #else |
| 8 | #define DBG_LOG(fmt, ...) \ |
| 9 | fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__) |
| 10 | #define DBG_ASSERT(cond) \ |
| 11 | ((cond) ? (void)0 : \ |
| 12 | fprintf(stderr, "ASSERT FAILED: %s at %s:%d\n", #cond, __FILE__, __LINE__)) |
| 13 | #endif |
| 14 | |
| 15 | // --- Platform Detection Pattern --- |
| 16 | #if defined(_WIN32) || defined(_WIN64) |
| 17 | #define PLATFORM_IS_WINDOWS 1 |
| 18 | #define PATH_SEP '\\' |
| 19 | #elif defined(__APPLE__) |
| 20 | #include <TargetConditionals.h> |
| 21 | #define PLATFORM_IS_MAC 1 |
| 22 | #define PATH_SEP '/' |
| 23 | #elif defined(__linux__) |
| 24 | #define PLATFORM_IS_LINUX 1 |
| 25 | #define PATH_SEP '/' |
| 26 | #else |
| 27 | #define PLATFORM_IS_UNKNOWN 1 |
| 28 | #define PATH_SEP '/' |
| 29 | #endif |
| 30 | |
| 31 | // --- Feature Toggle Pattern --- |
| 32 | #define FEATURE_CACHE (1 << 0) |
| 33 | #define FEATURE_LOGGING (1 << 1) |
| 34 | #define FEATURE_ENCRYPTION (1 << 2) |
| 35 | #define FEATURE_ALL (FEATURE_CACHE | FEATURE_LOGGING | FEATURE_ENCRYPTION) |
| 36 | |
| 37 | #define ENABLED_FEATURES (FEATURE_CACHE | FEATURE_LOGGING) |
| 38 | |
| 39 | #define HAS_FEATURE(f) ((ENABLED_FEATURES & (f)) == (f)) |
| 40 | |
| 41 | // --- Unused Parameter Silencing --- |
| 42 | #define UNUSED(x) ((void)(x)) |
| 43 | |
| 44 | int main(void) { |
| 45 | DBG_LOG("Starting application"); |
| 46 | DBG_ASSERT(sizeof(int) == 4); |
| 47 | |
| 48 | int unused_var = 42; |
| 49 | UNUSED(unused_var); |
| 50 | |
| 51 | printf("Platform: %d, Path sep: %c\n", |
| 52 | PLATFORM_IS_WINDOWS, PATH_SEP); |
| 53 | |
| 54 | #if HAS_FEATURE(FEATURE_CACHE) |
| 55 | printf("Cache feature enabled\n"); |
| 56 | #endif |
| 57 | |
| 58 | #if HAS_FEATURE(FEATURE_ENCRYPTION) |
| 59 | printf("Encryption enabled\n"); |
| 60 | #else |
| 61 | printf("Encryption disabled\n"); |
| 62 | #endif |
| 63 | |
| 64 | return 0; |
| 65 | } |
X-macros use a list of items defined as macros, then "include" that list multiple times with different definitions to generate multiple related code blocks from a single source of truth.
| 1 | #include <stdio.h> |
| 2 | #include <string.h> |
| 3 | |
| 4 | // Single source of truth: define all color entries |
| 5 | #define COLOR_LIST \ |
| 6 | X(RED, 0xFF0000, "Red") \ |
| 7 | X(GREEN, 0x00FF00, "Green") \ |
| 8 | X(BLUE, 0x0000FF, "Blue") \ |
| 9 | X(WHITE, 0xFFFFFF, "White") \ |
| 10 | X(BLACK, 0x000000, "Black") |
| 11 | |
| 12 | // Generate enum |
| 13 | #define X(name, value, str) name, |
| 14 | typedef enum { COLOR_LIST, COLOR_COUNT } Color; |
| 15 | #undef X |
| 16 | |
| 17 | // Generate name array |
| 18 | #define X(name, value, str) str, |
| 19 | static const char *color_names[] = { COLOR_LIST }; |
| 20 | #undef X |
| 21 | |
| 22 | // Generate value array |
| 23 | #define X(name, value, str) value, |
| 24 | static const unsigned int color_values[] = { COLOR_LIST }; |
| 25 | #undef X |
| 26 | |
| 27 | // Generate lookup function |
| 28 | const char *color_to_name(Color c) { |
| 29 | if (c < 0 || c >= COLOR_COUNT) return "Unknown"; |
| 30 | return color_names[c]; |
| 31 | } |
| 32 | |
| 33 | int name_to_color(const char *name) { |
| 34 | for (int i = 0; i < COLOR_COUNT; i++) { |
| 35 | if (strcmp(color_names[i], name) == 0) return i; |
| 36 | } |
| 37 | return -1; |
| 38 | } |
| 39 | |
| 40 | int main(void) { |
| 41 | printf("Colors: %d\n", COLOR_COUNT); |
| 42 | for (int i = 0; i < COLOR_COUNT; i++) { |
| 43 | printf(" %s = 0x%06X\n", color_names[i], color_values[i]); |
| 44 | } |
| 45 | |
| 46 | Color c = name_to_color("BLUE"); |
| 47 | printf("BLUE index: %d, value: 0x%06X\n", c, color_values[c]); |
| 48 | |
| 49 | return 0; |
| 50 | } |
pro tip
COLOR_LIST macro.Inline functions are almost always preferred over function-like macros. They provide type safety, proper scoping, single evaluation of arguments, and debugging support. Use macros only when you need preprocessor features like stringification, token pasting, or conditional compilation.
| Feature | Macro | Inline Function |
|---|---|---|
| Type safety | No — text substitution | Yes — type checked |
| Argument evaluation | Every occurrence | Once |
| Debugging | Cannot set breakpoints | Full debugger support |
| Scope | Global from point of define | Respects C scope rules |
| Stringification | Yes (# operator) | No |
| Token pasting | Yes (## operator) | No |
| Conditional compilation | Works in #if | No |
| Performance | Same or better | Same (compiler inlines) |
| 1 | #include <stdio.h> |
| 2 | |
| 3 | // Macro version: no type safety, double evaluation |
| 4 | #define MAX_MACRO(a, b) ((a) > (b) ? (a) : (b)) |
| 5 | |
| 6 | // Inline version: type safe, single evaluation |
| 7 | static inline int max_inline(int a, int b) { |
| 8 | return a > b ? a : b; |
| 9 | } |
| 10 | |
| 11 | // Generic macro: works with any type (C11 _Generic) |
| 12 | #define MAX_GENERIC(a, b) _Generic((a), \ |
| 13 | int: max_inline, \ |
| 14 | float: max_float, \ |
| 15 | double: max_double \ |
| 16 | )(a, b) |
| 17 | |
| 18 | static inline float max_float(float a, float b) { return a > b ? a : b; } |
| 19 | static inline double max_double(double a, double b) { return a > b ? a : b; } |
| 20 | |
| 21 | int main(void) { |
| 22 | int x = 10, y = 20; |
| 23 | |
| 24 | // Both work identically for simple cases |
| 25 | printf("Max: %d\n", MAX_MACRO(x, y)); |
| 26 | printf("Max: %d\n", max_inline(x, y)); |
| 27 | |
| 28 | // Generic macro handles multiple types |
| 29 | printf("Max: %d\n", MAX_GENERIC(x, y)); |
| 30 | |
| 31 | return 0; |
| 32 | } |
This template includes all best practices for header files: include guards, C++ compatibility, proper includes, and organized sections.
| 1 | #ifndef MYLIB_VECTOR_H |
| 2 | #define MYLIB_VECTOR_H |
| 3 | |
| 4 | // --- C++ compatibility --- |
| 5 | #ifdef __cplusplus |
| 6 | extern "C" { |
| 7 | #endif |
| 8 | |
| 9 | // --- Includes --- |
| 10 | #include <stddef.h> |
| 11 | #include <stdint.h> |
| 12 | |
| 13 | // --- Type definitions --- |
| 14 | typedef struct { |
| 15 | float x; |
| 16 | float y; |
| 17 | float z; |
| 18 | } Vec3; |
| 19 | |
| 20 | typedef struct { |
| 21 | float *data; |
| 22 | size_t size; |
| 23 | size_t capacity; |
| 24 | } VecBuffer; |
| 25 | |
| 26 | // --- Constants --- |
| 27 | #define VEC3_ZERO ((Vec3){0.0f, 0.0f, 0.0f}) |
| 28 | #define VEC3_ONE ((Vec3){1.0f, 1.0f, 1.0f}) |
| 29 | #define VEC3_UP ((Vec3){0.0f, 1.0f, 0.0f}) |
| 30 | |
| 31 | // --- Inline functions (header-only) --- |
| 32 | static inline Vec3 vec3_add(Vec3 a, Vec3 b) { |
| 33 | return (Vec3){a.x + b.x, a.y + b.y, a.z + b.z}; |
| 34 | } |
| 35 | |
| 36 | static inline Vec3 vec3_scale(Vec3 v, float s) { |
| 37 | return (Vec3){v.x * s, v.y * s, v.z * s}; |
| 38 | } |
| 39 | |
| 40 | static inline float vec3_dot(Vec3 a, Vec3 b) { |
| 41 | return a.x * b.x + a.y * b.y + a.z * b.z; |
| 42 | } |
| 43 | |
| 44 | // --- Function declarations --- |
| 45 | Vec3 vec3_cross(Vec3 a, Vec3 b); |
| 46 | float vec3_length(Vec3 v); |
| 47 | Vec3 vec3_normalize(Vec3 v); |
| 48 | |
| 49 | VecBuffer *vecbuf_create(size_t initial_capacity); |
| 50 | void vecbuf_destroy(VecBuffer *buf); |
| 51 | int vecbuf_push(VecBuffer *buf, float value); |
| 52 | |
| 53 | // --- C++ compatibility --- |
| 54 | #ifdef __cplusplus |
| 55 | } |
| 56 | #endif |
| 57 | |
| 58 | #endif /* MYLIB_VECTOR_H */ |
best practice
extern "C" wrappers, necessary includes, type definitions, macro constants, inline helper functions, and function declarations. Place in that order.Expressions in #if directives must be integer constant expressions. They support arithmetic, bitwise, and logical operators, but sizeof and enum values are not available in older preprocessors.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | #define VERSION 3 |
| 4 | #define DEBUG 1 |
| 5 | #define CONFIG_LEVEL 2 |
| 6 | |
| 7 | // Arithmetic in #if |
| 8 | #if VERSION > 2 |
| 9 | printf("Version 3+\n"); |
| 10 | #endif |
| 11 | |
| 12 | // Bitwise operations in #if |
| 13 | #define FLAGS 0x0F |
| 14 | #if (FLAGS & 0x01) != 0 |
| 15 | printf("Flag 0 set\n"); |
| 16 | #endif |
| 17 | |
| 18 | // Logical operators |
| 19 | #if DEBUG && CONFIG_LEVEL > 1 |
| 20 | printf("Debug level 2+\n"); |
| 21 | #endif |
| 22 | |
| 23 | // Token checks |
| 24 | #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L |
| 25 | printf("C11 or later\n"); |
| 26 | #endif |
| 27 | |
| 28 | // Nested ternary (not recommended, but valid) |
| 29 | #define MAX_VAL 10 |
| 30 | #if MAX_VAL > 20 ? 1 : 0 |
| 31 | printf("unlikely\n"); |
| 32 | #else |
| 33 | printf("MAX_VAL <= 20\n"); |
| 34 | #endif |
| 35 | |
| 36 | // char constants work in #if |
| 37 | #if 'A' == 65 |
| 38 | printf("ASCII system\n"); |
| 39 | #endif |
| 40 | |
| 41 | int main(void) { |
| 42 | printf("All preprocessor expression checks done\n"); |
| 43 | return 0; |
| 44 | } |