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
17 KiB
Chapter 15: Preprocessor and Macros in C
Complete Guide with Clay Library Examples
Table of Contents
- What is the Preprocessor?
- #include Directive
- #define - Simple Macros
- Conditional Compilation
- Header Guards
- Function-like Macros
- Multi-line Macros
- Stringification (#)
- Token Pasting (##)
- Predefined Macros
- Macro Best Practices
- 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:
- Remove comments
- Process #include directives
- Expand macros
- Handle conditional compilation
- 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:
- Current directory
- Directories specified with -I
- 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:
- First inclusion:
MYHEADER_Hnot defined, content is included - Second inclusion:
MYHEADER_Halready 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
- Create a DEBUG macro that prints file, line, and function name
- Write ARRAY_SIZE macro to get array length
- Implement MIN3 and MAX3 macros for 3 values
- Create a TYPEOF macro using _Generic (C11)
- Build a FOR_EACH macro to iterate arrays
- Design a BENCHMARK macro to time code execution
- Create platform-specific file path macros
- Implement a simple logging system with macros