|$ curl https://forge-ai.dev/api/markdown?path=docs/c/preprocessor
$cat docs/c-—-preprocessor.md
updated Recently·40 min read·published

C — Preprocessor

CPreprocessorMacrosIntermediateIntermediate🎯Free Tools
What the Preprocessor Does

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.

preprocess_basics.c
C
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
7int 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.

terminal
Bash
1# See preprocessor output (stops after preprocessing)
2gcc -E main.c -o main.i
3
4# With line markers for debugging
5gcc -E -C main.c -o main.i
6
7# Common flag combinations
8gcc -E -dM main.c # Dump all macro definitions
The #include Directive

#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.

include_example.c
C
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

Use angle brackets for all standard library headers. Use double quotes for your own project headers. This convention makes dependencies immediately clear.
#define — Object-like Macros

Object-like macros replace an identifier with a value. They are simple text substitutions with no type safety.

define_basics.c
C
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
#define — Function-like Macros

Function-like macros accept arguments and perform text substitution on each invocation. They look like function calls but have no type checking.

function_macros.c
C
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
16int 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}
Macro Hygiene: Parenthesization and Side Effects

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.

macro_hygiene.c
C
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
11int 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

A macro argument with side effects (like x++) will be evaluated every time it appears in the expansion. If the macro uses the argument more than once, the side effect fires multiple times. This is the most common macro bug in C.
The do while(0) Idiom

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.

do_while_idiom.c
C
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
15int 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 — Removing Macro Definitions

#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.

undef_example.c
C
1#include <stdio.h>
2
3#define LOG_LEVEL 2
4
5void 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
11int 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}
Stringification: The # Operator

The # operator converts a macro argument into a string literal. This is called "stringification" and is extremely useful for debugging and logging.

stringification.c
C
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
17int 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}
Token Concatenation: The ## Operator

The ## operator concatenates two tokens into a single token at preprocessing time. This is called "token pasting" and enables generating identifiers dynamically.

concatenation.c
C
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
17DECLARE_TEST(initialize); // Expands to: void test_initialize(void)
18DECLARE_TEST(cleanup); // Expands to: void test_cleanup(void)
19DECLARE_TEST(validation); // Expands to: void test_validation(void)
20
21// Registry of values
22#define REG_ENTRY(id, val) { id, val }
23
24struct entry {
25 int id;
26 int value;
27};
28
29struct entry table[] = {
30 REG_ENTRY(1, 100),
31 REG_ENTRY(2, 200),
32 REG_ENTRY(3, 300),
33};
34
35DEFINE_ACCESSOR(int, counter);
36DEFINE_ACCESSOR(float, ratio);
37
38int main(void) {
39 set_counter(42);
40 printf("counter = %d\n", get_counter());
41 return 0;
42}

warning

Do not leave spaces around ##. The operator requires the ## to directly touch both tokens.
Variadic Macros: __VA_ARGS__ (C99)

Variadic macros accept a variable number of arguments using the __VA_ARGS__ preprocessor identifier. The ellipsis ... in the parameter list captures all trailing arguments.

variadic_macros.c
C
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
26int 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}
Predefined Macros

The C standard defines several macros that the compiler provides automatically. These are invaluable for debugging, logging, and platform detection.

MacroDescriptionExample Value
__FILE__Current source file name"main.c"
__LINE__Current line number42
__func__Current function name (C99)"main"
__DATE__Compilation date"Jul 13 2026"
__TIME__Compilation time"14:30:00"
__STDC__Conforms to ISO C standard1
__STDC_VERSION__C standard version (C99+)201112L
__cplusplusDefined when compiled as C++199711L
predefined_macros.c
C
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
9typedef struct {
10 const char *file;
11 const char *date;
12 const char *time;
13 int std_version;
14} BuildInfo;
15
16BuildInfo 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
25int 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

Conditional compilation lets you include or exclude blocks of code based on macro definitions. This enables platform-specific code, feature toggles, and debug builds.

conditional_compilation.c
C
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
10int 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 in Header Files

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.

myheader.h
C
1// myheader.h — Proper include guard pattern
2#ifndef MYHEADER_H
3#define MYHEADER_H
4
5#include <stdint.h>
6
7// Type definitions
8typedef struct {
9 int32_t x;
10 int32_t y;
11 int32_t z;
12} Point3D;
13
14// Function declarations
15Point3D point_create(int32_t x, int32_t y, int32_t z);
16float point_distance(const Point3D *a, const Point3D *b);
17
18// Constants
19#define POINT_ORIGIN ((Point3D){0, 0, 0})
20
21#endif /* MYHEADER_H */
myheader_pragma.h
C
1// Alternative: #pragma once (non-standard but widely supported)
2#pragma once
3
4#include <stdint.h>
5
6typedef struct {
7 int32_t x;
8 int32_t y;
9 int32_t z;
10} Point3D;
11
12Point3D point_create(int32_t x, int32_t y, int32_t z);

best practice

Use traditional include guards (#ifndef / #define / #endif) for maximum portability. #pragma once is supported by GCC, Clang, and MSVC but is not part of the C standard.
#pragma — Compiler-Specific Directives

#pragma provides implementation-specific directives to the compiler. The standard guarantees that unknown pragmas are silently ignored.

pragma_example.c
C
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
6typedef 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"
16void 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\"")
28int main(void) {
29 return 0;
30}
#error and #warning Directives

#error halts compilation with an error message.#warning emits a warning (C23 standard, widely supported as extension). Both are essential for build-time validation.

error_warning.c
C
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
32int main(void) {
33 printf("All compile-time checks passed\n");
34 return 0;
35}
Common Patterns: Debug Logging, Platform Detection

These patterns appear frequently in production C codebases. Master them to write robust, portable software.

common_patterns.c
C
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
44int 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 Pattern for Code Generation

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.

x_macros.c
C
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,
14typedef enum { COLOR_LIST, COLOR_COUNT } Color;
15#undef X
16
17// Generate name array
18#define X(name, value, str) str,
19static const char *color_names[] = { COLOR_LIST };
20#undef X
21
22// Generate value array
23#define X(name, value, str) value,
24static const unsigned int color_values[] = { COLOR_LIST };
25#undef X
26
27// Generate lookup function
28const char *color_to_name(Color c) {
29 if (c < 0 || c >= COLOR_COUNT) return "Unknown";
30 return color_names[c];
31}
32
33int 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
40int 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

X-macros eliminate duplication when you need multiple representations (enum, string array, lookup table) of the same data. Adding a new entry requires editing only the COLOR_LIST macro.
Macro vs Inline Function

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.

FeatureMacroInline Function
Type safetyNo — text substitutionYes — type checked
Argument evaluationEvery occurrenceOnce
DebuggingCannot set breakpointsFull debugger support
ScopeGlobal from point of defineRespects C scope rules
StringificationYes (# operator)No
Token pastingYes (## operator)No
Conditional compilationWorks in #ifNo
PerformanceSame or betterSame (compiler inlines)
macro_vs_inline.c
C
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
7static 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
18static inline float max_float(float a, float b) { return a > b ? a : b; }
19static inline double max_double(double a, double b) { return a > b ? a : b; }
20
21int 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}
Complete Header File Template

This template includes all best practices for header files: include guards, C++ compatibility, proper includes, and organized sections.

vector.h
C
1#ifndef MYLIB_VECTOR_H
2#define MYLIB_VECTOR_H
3
4// --- C++ compatibility ---
5#ifdef __cplusplus
6extern "C" {
7#endif
8
9// --- Includes ---
10#include <stddef.h>
11#include <stdint.h>
12
13// --- Type definitions ---
14typedef struct {
15 float x;
16 float y;
17 float z;
18} Vec3;
19
20typedef 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) ---
32static 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
36static inline Vec3 vec3_scale(Vec3 v, float s) {
37 return (Vec3){v.x * s, v.y * s, v.z * s};
38}
39
40static 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 ---
45Vec3 vec3_cross(Vec3 a, Vec3 b);
46float vec3_length(Vec3 v);
47Vec3 vec3_normalize(Vec3 v);
48
49VecBuffer *vecbuf_create(size_t initial_capacity);
50void vecbuf_destroy(VecBuffer *buf);
51int vecbuf_push(VecBuffer *buf, float value);
52
53// --- C++ compatibility ---
54#ifdef __cplusplus
55}
56#endif
57
58#endif /* MYLIB_VECTOR_H */

best practice

Every header file should include: include guards, C++ extern "C" wrappers, necessary includes, type definitions, macro constants, inline helper functions, and function declarations. Place in that order.
Preprocessor Expressions in #if

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.

preprocessor_expressions.c
C
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
41int main(void) {
42 printf("All preprocessor expression checks done\n");
43 return 0;
44}
$Blueprint — Engineering Documentation·Section ID: C-PREPROCESSOR·Revision: 1.0