clay/docs/15_preprocessor_macros.md
Claude f793695503
Add advanced C programming tutorials: preprocessor, macros, and memory management
Comprehensive tutorials for advanced C topics:

- 15_preprocessor_macros.md (800+ lines)
  * Complete preprocessor guide
  * #include, #define, conditional compilation
  * Header guards, function-like macros
  * Multi-line macros, stringification, token pasting
  * Predefined macros, best practices
  * Extensive Clay examples showing macro patterns

- 16_advanced_macros.md (900+ lines)
  * Variadic macros with __VA_ARGS__
  * X-Macros pattern for code generation
  * _Generic for type-based selection (C11)
  * Compound literals and statement expressions
  * For-loop macro trick (Clay's CLAY() macro explained)
  * Designated initializers in macros
  * Recursive macro techniques
  * Complete breakdown of Clay's macro system

- 17_memory_management.md (850+ lines)
  * Stack vs heap memory comparison
  * malloc, calloc, realloc, free usage
  * Common memory errors and prevention
  * Memory leak detection with Valgrind
  * Arena allocators (Clay's approach)
  * Memory pools for performance
  * Memory alignment optimization
  * Custom allocators
  * Clay's zero-allocation strategy
  * Best practices and profiling

All files include:
- 50+ code examples per chapter
- Real Clay library usage throughout
- Practice exercises
- Performance considerations
- Professional patterns

Total new content: ~2,500 lines of detailed tutorials
2025-11-14 06:40:51 +00:00

17 KiB

Chapter 15: Preprocessor and Macros in C

Complete Guide with Clay Library Examples


Table of Contents

  1. What is the Preprocessor?
  2. #include Directive
  3. #define - Simple Macros
  4. Conditional Compilation
  5. Header Guards
  6. Function-like Macros
  7. Multi-line Macros
  8. Stringification (#)
  9. Token Pasting (##)
  10. Predefined Macros
  11. Macro Best Practices
  12. Clay's Macro Usage

15.1 What is the Preprocessor?

The preprocessor runs before compilation and performs text substitution.

// Source file (.c)
#define MAX 100

int main(void) {
    int arr[MAX];  // Preprocessor replaces MAX with 100
    return 0;
}

// After preprocessing
int main(void) {
    int arr[100];
    return 0;
}

Preprocessing steps:

  1. Remove comments
  2. Process #include directives
  3. Expand macros
  4. Handle conditional compilation
  5. Output preprocessed code to compiler

View preprocessed output:

gcc -E program.c        # Output to stdout
gcc -E program.c -o program.i  # Save to file

15.2 #include Directive

Include header files in your code.

Standard Library Headers

#include <stdio.h>   // System header (angle brackets)
#include <stdlib.h>
#include <string.h>

Search path: System include directories (/usr/include, etc.)

User Headers

#include "myheader.h"  // User header (quotes)
#include "utils/math.h"

Search path:

  1. Current directory
  2. Directories specified with -I
  3. System directories

How #include Works

// Before preprocessing
#include "myheader.h"

int main(void) {
    // code
}

// After preprocessing (myheader.h contents inserted)
// Contents of myheader.h
void myFunction(void);
typedef struct { int x, y; } Point;

int main(void) {
    // code
}

Clay Example (from clay.h):

// Clay is a single-header library
#define CLAY_IMPLEMENTATION
#include "clay.h"

// No other includes needed - zero dependencies!

15.3 #define - Simple Macros

Define constants and simple text replacements.

Constant Macros

#define PI 3.14159
#define MAX_SIZE 1000
#define BUFFER_SIZE 256
#define VERSION "1.0.0"

int main(void) {
    float radius = 5.0f;
    float area = PI * radius * radius;  // PI replaced with 3.14159

    char buffer[BUFFER_SIZE];
    return 0;
}

Clay Example (from clay.h:102):

#define CLAY_VERSION_MAJOR 0
#define CLAY_VERSION_MINOR 12
#define CLAY_VERSION_PATCH 0

// Used to check version at compile time
#if CLAY_VERSION_MAJOR >= 1
    // Version 1.0 or higher
#endif

Expression Macros

#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define IS_EVEN(n) (((n) & 1) == 0)

int main(void) {
    int sq = SQUARE(5);       // 25
    int max = MAX(10, 20);    // 20
    int even = IS_EVEN(4);    // 1 (true)
    return 0;
}

Important: Always use parentheses!

// WRONG
#define SQUARE(x) x * x
int result = SQUARE(2 + 3);  // Expands to: 2 + 3 * 2 + 3 = 11 (wrong!)

// RIGHT
#define SQUARE(x) ((x) * (x))
int result = SQUARE(2 + 3);  // Expands to: ((2 + 3) * (2 + 3)) = 25 (correct!)

Clay Example (from clay.h:111):

#define CLAY__MAX(x, y) (((x) > (y)) ? (x) : (y))
#define CLAY__MIN(x, y) (((x) < (y)) ? (x) : (y))

// Usage in Clay's layout calculations
float width = CLAY__MAX(minWidth, calculatedWidth);

15.4 Conditional Compilation

Compile different code based on conditions.

#ifdef and #ifndef

#define DEBUG

int main(void) {
    #ifdef DEBUG
        printf("Debug mode enabled\n");
    #endif

    #ifndef RELEASE
        printf("Not in release mode\n");
    #endif

    return 0;
}

#if, #elif, #else, #endif

#define VERSION 2

#if VERSION == 1
    printf("Version 1\n");
#elif VERSION == 2
    printf("Version 2\n");
#else
    printf("Unknown version\n");
#endif

defined() Operator

#if defined(DEBUG) && defined(VERBOSE)
    printf("Debug and verbose mode\n");
#endif

// Equivalent to
#if defined(DEBUG)
    #if defined(VERBOSE)
        printf("Debug and verbose mode\n");
    #endif
#endif

Clay Example (from clay.h:88):

// Platform-specific exports
#if defined(_MSC_VER)
    #define CLAY_WASM __declspec(dllexport)
#elif defined(__GNUC__) || defined(__clang__)
    #define CLAY_WASM __attribute__((visibility("default")))
#else
    #define CLAY_WASM
#endif

Platform Detection

#ifdef _WIN32
    #include <windows.h>
    #define SLEEP(ms) Sleep(ms)
#elif defined(__linux__)
    #include <unistd.h>
    #define SLEEP(ms) usleep((ms) * 1000)
#elif defined(__APPLE__)
    #include <unistd.h>
    #define SLEEP(ms) usleep((ms) * 1000)
#endif

int main(void) {
    SLEEP(1000);  // Sleep 1 second on any platform
    return 0;
}

Clay Example (from clay.h):

// SIMD optimization based on platform
#if !defined(CLAY_DISABLE_SIMD)
    #if defined(__x86_64__) || defined(_M_X64)
        #include <emmintrin.h>
        // Use SSE2 instructions
    #elif defined(__aarch64__)
        #include <arm_neon.h>
        // Use NEON instructions
    #endif
#endif

15.5 Header Guards

Prevent multiple inclusion of same header.

Traditional Header Guards

// myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H

// Header contents
void myFunction(void);

typedef struct {
    int x, y;
} Point;

#endif // MYHEADER_H

How it works:

  1. First inclusion: MYHEADER_H not defined, content is included
  2. Second inclusion: MYHEADER_H already defined, content skipped

#pragma once (Non-standard but widely supported)

// myheader.h
#pragma once

// Header contents
void myFunction(void);

Clay Example (from clay.h:82):

#ifndef CLAY_HEADER
#define CLAY_HEADER

// All Clay declarations (4000+ lines)

typedef struct {
    float x, y;
} Clay_Vector2;

// ... more declarations

#endif // CLAY_HEADER

15.6 Function-like Macros

Macros that look like functions.

Basic Function-like Macros

#define ABS(x) ((x) < 0 ? -(x) : (x))
#define SQUARE(x) ((x) * (x))
#define SWAP(a, b, type) { type temp = a; a = b; b = temp; }

int main(void) {
    int abs_val = ABS(-5);        // 5
    int squared = SQUARE(4);      // 16

    int x = 10, y = 20;
    SWAP(x, y, int);
    printf("%d %d\n", x, y);      // 20 10

    return 0;
}

Macros vs Functions

Advantages of macros:

  • No function call overhead
  • Type-generic (works with any type)
  • Can access local variables

Disadvantages:

  • Code bloat (expanded everywhere)
  • No type checking
  • Side effects with multiple evaluation
  • Harder to debug

Multiple Evaluation Problem

#define SQUARE(x) ((x) * (x))

int main(void) {
    int n = 5;
    int result = SQUARE(n++);
    // Expands to: ((n++) * (n++))
    // n is incremented TWICE!
    printf("n = %d, result = %d\n", n, result);  // n = 7, result = 30
    return 0;
}

Solution: Use inline functions when possible:

static inline int square(int x) {
    return x * x;
}

int main(void) {
    int n = 5;
    int result = square(n++);  // Only increments once
    printf("n = %d, result = %d\n", n, result);  // n = 6, result = 25
    return 0;
}

Clay Example (from clay.h):

// Clay uses inline functions when type safety matters
static inline float Clay__Min(float a, float b) {
    return a < b ? a : b;
}

static inline float Clay__Max(float a, float b) {
    return a > b ? a : b;
}

// But uses macros for compile-time constants
#define CLAY_PADDING_ALL(amount) \
    (Clay_Padding){amount, amount, amount, amount}

15.7 Multi-line Macros

Use backslash to continue lines.

Basic Multi-line Macro

#define PRINT_COORDS(x, y) \
    printf("X: %d\n", x); \
    printf("Y: %d\n", y);

int main(void) {
    PRINT_COORDS(10, 20);
    return 0;
}

do-while(0) Trick

Makes macros behave like statements:

// WRONG - breaks in if statements
#define SWAP(a, b, type) \
    type temp = a; \
    a = b; \
    b = temp;

if (condition)
    SWAP(x, y, int);  // Syntax error!
else
    other();

// RIGHT - works everywhere
#define SWAP(a, b, type) \
    do { \
        type temp = a; \
        a = b; \
        b = temp; \
    } while(0)

if (condition)
    SWAP(x, y, int);  // Works correctly!
else
    other();

Why it works:

  • do { } while(0) is a single statement
  • Can be used anywhere a statement is expected
  • while(0) ensures it runs exactly once
  • Semicolon at end required by user

Clay Example (from clay.h):

#define CLAY__ASSERT(condition) \
    do { \
        if (!(condition)) { \
            Clay__currentContext->errorHandler.errorHandlerFunction( \
                CLAY__INIT(Clay_ErrorData) { \
                    .errorType = CLAY_ERROR_TYPE_ASSERTION_FAILED, \
                    .errorText = CLAY_STRING("Assertion failed: " #condition), \
                    .userData = Clay__currentContext->errorHandler.userData \
                } \
            ); \
        } \
    } while(0)

// Usage
CLAY__ASSERT(element != NULL);  // Works correctly

15.8 Stringification (#)

Convert macro argument to string.

Basic Stringification

#define TO_STRING(x) #x

int main(void) {
    printf("%s\n", TO_STRING(Hello));       // "Hello"
    printf("%s\n", TO_STRING(123));         // "123"
    printf("%s\n", TO_STRING(int x = 10));  // "int x = 10"
    return 0;
}

Practical Example

#define PRINT_VAR(var) printf(#var " = %d\n", var)

int main(void) {
    int age = 25;
    int count = 100;

    PRINT_VAR(age);    // Prints: age = 25
    PRINT_VAR(count);  // Prints: count = 100

    return 0;
}

Double Stringification

#define STRINGIFY(x) #x
#define TO_STRING(x) STRINGIFY(x)

#define VERSION_MAJOR 1
#define VERSION_MINOR 2
#define VERSION_PATCH 3

#define VERSION_STRING \
    TO_STRING(VERSION_MAJOR) "." \
    TO_STRING(VERSION_MINOR) "." \
    TO_STRING(VERSION_PATCH)

int main(void) {
    printf("Version: %s\n", VERSION_STRING);  // "Version: 1.2.3"
    return 0;
}

Clay Example (from clay.h):

#define CLAY__STRINGIFY(x) #x
#define CLAY__VERSION_STRING \
    CLAY__STRINGIFY(CLAY_VERSION_MAJOR) "." \
    CLAY__STRINGIFY(CLAY_VERSION_MINOR) "." \
    CLAY__STRINGIFY(CLAY_VERSION_PATCH)

// Results in version string "0.12.0"

15.9 Token Pasting (##)

Concatenate tokens to create new identifiers.

Basic Token Pasting

#define CONCAT(a, b) a##b

int main(void) {
    int xy = 100;
    int value = CONCAT(x, y);  // Becomes: xy
    printf("%d\n", value);     // 100

    return 0;
}

Generate Variable Names

#define VAR(name, index) name##index

int main(void) {
    int VAR(value, 1) = 10;  // int value1 = 10;
    int VAR(value, 2) = 20;  // int value2 = 20;
    int VAR(value, 3) = 30;  // int value3 = 30;

    printf("%d %d %d\n", value1, value2, value3);
    return 0;
}

Generate Function Names

#define DEFINE_GETTER(type, name) \
    type get_##name(void) { \
        return global_##name; \
    }

int global_count = 10;
float global_value = 3.14f;

DEFINE_GETTER(int, count)    // Creates: int get_count(void)
DEFINE_GETTER(float, value)  // Creates: float get_value(void)

int main(void) {
    printf("%d\n", get_count());   // 10
    printf("%.2f\n", get_value()); // 3.14
    return 0;
}

Clay Example (from clay.h):

// Generate array types for different types
#define CLAY__ARRAY_DEFINE(typeName, arrayName) \
    typedef struct { \
        int32_t capacity; \
        int32_t length; \
        typeName *internalArray; \
    } arrayName;

// Creates multiple array types
CLAY__ARRAY_DEFINE(int32_t, Clay__int32_tArray)
CLAY__ARRAY_DEFINE(Clay_String, Clay__StringArray)
CLAY__ARRAY_DEFINE(Clay_RenderCommand, Clay__RenderCommandArray)

15.10 Predefined Macros

C provides built-in macros.

Standard Predefined Macros

#include <stdio.h>

int main(void) {
    printf("File: %s\n", __FILE__);       // Current file name
    printf("Line: %d\n", __LINE__);       // Current line number
    printf("Date: %s\n", __DATE__);       // Compilation date
    printf("Time: %s\n", __TIME__);       // Compilation time
    printf("Function: %s\n", __func__);   // Current function (C99)

    #ifdef __STDC__
        printf("Standard C: Yes\n");
    #endif

    #ifdef __STDC_VERSION__
        printf("C Version: %ld\n", __STDC_VERSION__);
    #endif

    return 0;
}

Platform Predefined Macros

int main(void) {
    #ifdef _WIN32
        printf("Windows\n");
    #endif

    #ifdef __linux__
        printf("Linux\n");
    #endif

    #ifdef __APPLE__
        printf("macOS\n");
    #endif

    #ifdef __x86_64__
        printf("64-bit x86\n");
    #endif

    #ifdef __aarch64__
        printf("64-bit ARM\n");
    #endif

    return 0;
}

Compiler Predefined Macros

int main(void) {
    #ifdef __GNUC__
        printf("GCC version: %d.%d.%d\n",
               __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
    #endif

    #ifdef _MSC_VER
        printf("MSVC version: %d\n", _MSC_VER);
    #endif

    #ifdef __clang__
        printf("Clang version: %s\n", __clang_version__);
    #endif

    return 0;
}

15.11 Macro Best Practices

Rule 1: Always Use Parentheses

// WRONG
#define MULTIPLY(a, b) a * b
int result = MULTIPLY(2 + 3, 4 + 5);  // 2 + 3 * 4 + 5 = 19 (wrong!)

// RIGHT
#define MULTIPLY(a, b) ((a) * (b))
int result = MULTIPLY(2 + 3, 4 + 5);  // (2 + 3) * (4 + 5) = 45 (correct!)

Rule 2: Avoid Side Effects

// DANGEROUS
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int x = 5;
int max = MAX(x++, 10);  // x incremented multiple times!

// SAFER - use inline function
static inline int max(int a, int b) {
    return a > b ? a : b;
}

int x = 5;
int max_val = max(x++, 10);  // x incremented once

Rule 3: Use UPPERCASE for Macros

// Makes it clear this is a macro
#define BUFFER_SIZE 256
#define MAX(a, b) ((a) > (b) ? (a) : (b))

// Not a macro
static inline int max(int a, int b) { return a > b ? a : b; }

Rule 4: Prefer inline Functions

// Macro (older style)
#define SQUARE(x) ((x) * (x))

// inline function (modern, preferred)
static inline int square(int x) {
    return x * x;
}

When to use macros:

  • Constants
  • Conditional compilation
  • Type-generic operations
  • Code generation

When to use inline functions:

  • Type-safe operations
  • Complex logic
  • Avoid side effects

15.12 Clay's Macro Usage

Configuration Macros

// User can override before including
#define CLAY_MAX_ELEMENT_COUNT 8192
#define CLAY_MAX_MEASURETEXT_CACHE_SIZE 16384

#define CLAY_IMPLEMENTATION
#include "clay.h"

Initialization Macros

// Compound literal initialization
#define CLAY__INIT(type) (type)

Clay_Color red = CLAY__INIT(Clay_Color) {
    .r = 255, .g = 0, .b = 0, .a = 255
};

String Macros

// Create Clay_String from literal
#define CLAY_STRING(stringContents) \
    (Clay_String){ \
        .length = sizeof(stringContents) - 1, \
        .chars = (stringContents) \
    }

// Usage
Clay_String title = CLAY_STRING("My Application");

Layout Macros

// Convenient padding creation
#define CLAY_PADDING_ALL(amount) \
    (Clay_Padding){amount, amount, amount, amount}

#define CLAY_PADDING(left, right, top, bottom) \
    (Clay_Padding){left, right, top, bottom}

// Usage
Clay_Padding pad = CLAY_PADDING_ALL(16);

ID Macros

// Generate element IDs
#define CLAY_ID(label) \
    Clay__HashString(CLAY_STRING(label), 0, 0)

#define CLAY_IDI(label, index) \
    Clay__HashString(CLAY_STRING(label), index, 0)

// Usage
Clay_ElementId buttonId = CLAY_ID("SubmitButton");
Clay_ElementId itemId = CLAY_IDI("ListItem", i);

15.13 Complete Clay Macro Example

#define CLAY_IMPLEMENTATION
#include "clay.h"

int main(void) {
    // String macro
    Clay_String title = CLAY_STRING("Clay Demo");

    // Color macro
    Clay_Color bgColor = CLAY__INIT(Clay_Color) {
        .r = 200, .g = 200, .b = 200, .a = 255
    };

    // Padding macro
    Clay_Padding padding = CLAY_PADDING_ALL(16);

    // ID macro
    Clay_ElementId rootId = CLAY_ID("Root");

    // Use in layout
    CLAY(CLAY_ID("Container"),
         CLAY_LAYOUT({
             .padding = CLAY_PADDING_ALL(8),
             .sizing = {
                 .width = CLAY_SIZING_GROW(0),
                 .height = CLAY_SIZING_GROW(0)
             }
         }),
         CLAY_RECTANGLE_CONFIG({
             .color = bgColor
         })) {
        CLAY_TEXT(title, CLAY_TEXT_CONFIG({
            .fontSize = 24,
            .textColor = CLAY__INIT(Clay_Color){0, 0, 0, 255}
        }));
    }

    return 0;
}

15.14 Key Concepts Learned

  • Preprocessor runs before compilation
  • #include inserts file contents
  • #define creates macros
  • Conditional compilation (#ifdef, #if)
  • Header guards prevent multiple inclusion
  • Function-like macros with parameters
  • Multi-line macros with do-while(0)
  • Stringification with #
  • Token pasting with ##
  • Predefined macros (FILE, LINE)
  • Best practices and pitfalls
  • Clay's effective macro usage

Practice Exercises

  1. Create a DEBUG macro that prints file, line, and function name
  2. Write ARRAY_SIZE macro to get array length
  3. Implement MIN3 and MAX3 macros for 3 values
  4. Create a TYPEOF macro using _Generic (C11)
  5. Build a FOR_EACH macro to iterate arrays
  6. Design a BENCHMARK macro to time code execution
  7. Create platform-specific file path macros
  8. Implement a simple logging system with macros