mirror of
https://github.com/nicbarker/clay.git
synced 2025-12-23 17:41:06 +00:00
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
This commit is contained in:
parent
faea55a9b9
commit
f793695503
922
docs/15_preprocessor_macros.md
Normal file
922
docs/15_preprocessor_macros.md
Normal file
|
|
@ -0,0 +1,922 @@
|
|||
# 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.
|
||||
|
||||
```c
|
||||
// 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:**
|
||||
```bash
|
||||
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
|
||||
|
||||
```c
|
||||
#include <stdio.h> // System header (angle brackets)
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
```
|
||||
|
||||
**Search path:** System include directories (/usr/include, etc.)
|
||||
|
||||
### User Headers
|
||||
|
||||
```c
|
||||
#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
|
||||
|
||||
```c
|
||||
// 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):
|
||||
```c
|
||||
// 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
|
||||
|
||||
```c
|
||||
#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):
|
||||
```c
|
||||
#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
|
||||
|
||||
```c
|
||||
#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!
|
||||
|
||||
```c
|
||||
// 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):
|
||||
```c
|
||||
#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
|
||||
|
||||
```c
|
||||
#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
|
||||
|
||||
```c
|
||||
#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
|
||||
|
||||
```c
|
||||
#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):
|
||||
```c
|
||||
// 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
|
||||
|
||||
```c
|
||||
#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):
|
||||
```c
|
||||
// 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
|
||||
|
||||
```c
|
||||
// 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)
|
||||
|
||||
```c
|
||||
// myheader.h
|
||||
#pragma once
|
||||
|
||||
// Header contents
|
||||
void myFunction(void);
|
||||
```
|
||||
|
||||
**Clay Example** (from clay.h:82):
|
||||
```c
|
||||
#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
|
||||
|
||||
```c
|
||||
#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
|
||||
|
||||
```c
|
||||
#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:
|
||||
|
||||
```c
|
||||
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):
|
||||
```c
|
||||
// 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
|
||||
|
||||
```c
|
||||
#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:
|
||||
|
||||
```c
|
||||
// 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):
|
||||
```c
|
||||
#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
|
||||
|
||||
```c
|
||||
#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
|
||||
|
||||
```c
|
||||
#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
|
||||
|
||||
```c
|
||||
#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):
|
||||
```c
|
||||
#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
|
||||
|
||||
```c
|
||||
#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
|
||||
|
||||
```c
|
||||
#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
|
||||
|
||||
```c
|
||||
#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):
|
||||
```c
|
||||
// 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
|
||||
|
||||
```c
|
||||
#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
|
||||
|
||||
```c
|
||||
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
|
||||
|
||||
```c
|
||||
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
|
||||
|
||||
```c
|
||||
// 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
|
||||
|
||||
```c
|
||||
// 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
|
||||
|
||||
```c
|
||||
// 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
|
||||
|
||||
```c
|
||||
// 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
|
||||
|
||||
```c
|
||||
// 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
|
||||
|
||||
```c
|
||||
// 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
|
||||
|
||||
```c
|
||||
// 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
|
||||
|
||||
```c
|
||||
// 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
|
||||
|
||||
```c
|
||||
// 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
|
||||
|
||||
```c
|
||||
#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
|
||||
847
docs/16_advanced_macros.md
Normal file
847
docs/16_advanced_macros.md
Normal file
|
|
@ -0,0 +1,847 @@
|
|||
# Chapter 16: Advanced Macros and Metaprogramming in C
|
||||
|
||||
## Complete Guide with Clay Library Examples
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
1. Variadic Macros
|
||||
2. X-Macros Pattern
|
||||
3. _Generic for Type Selection (C11)
|
||||
4. Compound Literals
|
||||
5. Statement Expressions (GCC)
|
||||
6. For-Loop Macro Trick
|
||||
7. Designated Initializers in Macros
|
||||
8. Recursive Macros
|
||||
9. Macro Debugging
|
||||
10. Clay's Advanced Macro Techniques
|
||||
|
||||
---
|
||||
|
||||
## 16.1 Variadic Macros
|
||||
|
||||
Macros that accept variable number of arguments.
|
||||
|
||||
### Basic Variadic Macro
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
#define LOG(format, ...) \
|
||||
printf("[LOG] " format "\n", __VA_ARGS__)
|
||||
|
||||
#define ERROR(format, ...) \
|
||||
fprintf(stderr, "[ERROR] " format "\n", __VA_ARGS__)
|
||||
|
||||
int main(void) {
|
||||
LOG("Program started");
|
||||
LOG("Value: %d", 42);
|
||||
LOG("X: %d, Y: %d", 10, 20);
|
||||
|
||||
ERROR("File not found: %s", "config.txt");
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**`__VA_ARGS__`** represents all variadic arguments.
|
||||
|
||||
### Empty Variadic Arguments Problem
|
||||
|
||||
```c
|
||||
// Problem: No arguments after format
|
||||
#define LOG(format, ...) printf(format, __VA_ARGS__)
|
||||
|
||||
LOG("Hello"); // ERROR: too few arguments (missing comma)
|
||||
|
||||
// Solution 1: GCC ##__VA_ARGS__ extension
|
||||
#define LOG(format, ...) printf(format, ##__VA_ARGS__)
|
||||
|
||||
LOG("Hello"); // Works
|
||||
LOG("Value: %d", 5); // Also works
|
||||
|
||||
// Solution 2: __VA_OPT__ (C++20, C23)
|
||||
#define LOG(format, ...) printf(format __VA_OPT__(,) __VA_ARGS__)
|
||||
```
|
||||
|
||||
### Named Variadic Arguments
|
||||
|
||||
```c
|
||||
#define DEBUG_PRINT(level, format, ...) \
|
||||
do { \
|
||||
if (debug_level >= level) { \
|
||||
printf("[%d] " format "\n", level, __VA_ARGS__); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
int debug_level = 2;
|
||||
|
||||
int main(void) {
|
||||
DEBUG_PRINT(1, "Low priority: %s", "message");
|
||||
DEBUG_PRINT(3, "High priority: %d", 42);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Counting Arguments
|
||||
|
||||
```c
|
||||
// Count macro arguments (up to 10)
|
||||
#define COUNT_ARGS(...) \
|
||||
COUNT_ARGS_IMPL(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
|
||||
|
||||
#define COUNT_ARGS_IMPL(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,N,...) N
|
||||
|
||||
int main(void) {
|
||||
int count1 = COUNT_ARGS(a); // 1
|
||||
int count2 = COUNT_ARGS(a, b, c); // 3
|
||||
int count3 = COUNT_ARGS(a, b, c, d, e); // 5
|
||||
|
||||
printf("%d %d %d\n", count1, count2, count3);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**Clay Example** (from clay.h):
|
||||
```c
|
||||
// Clay's CLAY() macro uses variadic arguments
|
||||
#define CLAY(...) \
|
||||
for (CLAY__ELEMENT_DEFINITION_LATCH = ( \
|
||||
Clay__OpenElement(), \
|
||||
Clay__ConfigureOpenElement(__VA_ARGS__), \
|
||||
0 \
|
||||
); \
|
||||
CLAY__ELEMENT_DEFINITION_LATCH < 1; \
|
||||
CLAY__ELEMENT_DEFINITION_LATCH = 1, Clay__CloseElement())
|
||||
|
||||
// Usage with variable configs
|
||||
CLAY(CLAY_ID("Button")) { }
|
||||
CLAY(CLAY_ID("Box"), CLAY_LAYOUT(.padding = 8)) { }
|
||||
CLAY(CLAY_ID("Panel"), CLAY_LAYOUT(...), CLAY_RECTANGLE(...)) { }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16.2 X-Macros Pattern
|
||||
|
||||
Generate code from data tables.
|
||||
|
||||
### Basic X-Macro
|
||||
|
||||
```c
|
||||
// Define data once
|
||||
#define COLOR_TABLE \
|
||||
X(RED, 0xFF0000) \
|
||||
X(GREEN, 0x00FF00) \
|
||||
X(BLUE, 0x0000FF) \
|
||||
X(YELLOW, 0xFFFF00) \
|
||||
X(CYAN, 0x00FFFF) \
|
||||
X(MAGENTA, 0xFF00FF)
|
||||
|
||||
// Generate enum
|
||||
typedef enum {
|
||||
#define X(name, value) COLOR_##name,
|
||||
COLOR_TABLE
|
||||
#undef X
|
||||
COLOR_COUNT
|
||||
} Color;
|
||||
|
||||
// Generate array of values
|
||||
const unsigned int colorValues[] = {
|
||||
#define X(name, value) value,
|
||||
COLOR_TABLE
|
||||
#undef X
|
||||
};
|
||||
|
||||
// Generate array of names
|
||||
const char* colorNames[] = {
|
||||
#define X(name, value) #name,
|
||||
COLOR_TABLE
|
||||
#undef X
|
||||
};
|
||||
|
||||
int main(void) {
|
||||
printf("Color: %s = 0x%06X\n",
|
||||
colorNames[COLOR_RED],
|
||||
colorValues[COLOR_RED]);
|
||||
|
||||
printf("Total colors: %d\n", COLOR_COUNT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Error Code System with X-Macros
|
||||
|
||||
```c
|
||||
#define ERROR_CODES \
|
||||
X(OK, 0, "Success") \
|
||||
X(FILE_NOT_FOUND, 1, "File not found") \
|
||||
X(ACCESS_DENIED, 2, "Access denied") \
|
||||
X(OUT_OF_MEMORY, 3, "Out of memory") \
|
||||
X(INVALID_ARGUMENT, 4, "Invalid argument")
|
||||
|
||||
// Generate enum
|
||||
typedef enum {
|
||||
#define X(name, code, msg) ERR_##name = code,
|
||||
ERROR_CODES
|
||||
#undef X
|
||||
} ErrorCode;
|
||||
|
||||
// Generate error messages
|
||||
const char* getErrorMessage(ErrorCode err) {
|
||||
switch (err) {
|
||||
#define X(name, code, msg) case ERR_##name: return msg;
|
||||
ERROR_CODES
|
||||
#undef X
|
||||
default: return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
ErrorCode err = ERR_FILE_NOT_FOUND;
|
||||
printf("Error %d: %s\n", err, getErrorMessage(err));
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Clay Example (conceptual):
|
||||
```c
|
||||
// Clay could use X-macros for element types
|
||||
#define CLAY_ELEMENT_TYPES \
|
||||
X(RECTANGLE, "Rectangle") \
|
||||
X(TEXT, "Text") \
|
||||
X(IMAGE, "Image") \
|
||||
X(BORDER, "Border") \
|
||||
X(CUSTOM, "Custom")
|
||||
|
||||
typedef enum {
|
||||
#define X(name, str) CLAY_ELEMENT_TYPE_##name,
|
||||
CLAY_ELEMENT_TYPES
|
||||
#undef X
|
||||
} Clay_ElementType;
|
||||
|
||||
const char* Clay__ElementTypeToString(Clay_ElementType type) {
|
||||
switch (type) {
|
||||
#define X(name, str) case CLAY_ELEMENT_TYPE_##name: return str;
|
||||
CLAY_ELEMENT_TYPES
|
||||
#undef X
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16.3 _Generic for Type Selection (C11)
|
||||
|
||||
Compile-time type-based selection.
|
||||
|
||||
### Basic _Generic
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
#define print(x) _Generic((x), \
|
||||
int: printf("%d\n", x), \
|
||||
float: printf("%.2f\n", x), \
|
||||
double: printf("%.4f\n", x), \
|
||||
char*: printf("%s\n", x), \
|
||||
default: printf("Unknown type\n") \
|
||||
)
|
||||
|
||||
int main(void) {
|
||||
print(42); // Prints: 42
|
||||
print(3.14f); // Prints: 3.14
|
||||
print(2.71828); // Prints: 2.7183
|
||||
print("Hello"); // Prints: Hello
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Type-Safe Generic Max
|
||||
|
||||
```c
|
||||
#define max(a, b) _Generic((a), \
|
||||
int: max_int, \
|
||||
long: max_long, \
|
||||
float: max_float, \
|
||||
double: max_double \
|
||||
)(a, b)
|
||||
|
||||
static inline int max_int(int a, int b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
static inline long max_long(long a, long b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
static inline float max_float(float a, float b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
static inline double max_double(double a, double b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int i = max(5, 10); // Calls max_int
|
||||
float f = max(3.14f, 2.71f); // Calls max_float
|
||||
double d = max(1.1, 2.2); // Calls max_double
|
||||
|
||||
printf("%d %.2f %.2f\n", i, f, d);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Type Identification
|
||||
|
||||
```c
|
||||
#define typename(x) _Generic((x), \
|
||||
int: "int", \
|
||||
float: "float", \
|
||||
double: "double", \
|
||||
char: "char", \
|
||||
char*: "char*", \
|
||||
int*: "int*", \
|
||||
default: "unknown" \
|
||||
)
|
||||
|
||||
int main(void) {
|
||||
int x = 10;
|
||||
float y = 3.14f;
|
||||
char* str = "hello";
|
||||
|
||||
printf("x is %s\n", typename(x)); // "int"
|
||||
printf("y is %s\n", typename(y)); // "float"
|
||||
printf("str is %s\n", typename(str)); // "char*"
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16.4 Compound Literals
|
||||
|
||||
Create temporary struct/array values.
|
||||
|
||||
### Basic Compound Literals
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
int x, y;
|
||||
} Point;
|
||||
|
||||
void printPoint(Point p) {
|
||||
printf("(%d, %d)\n", p.x, p.y);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
// Compound literal
|
||||
printPoint((Point){10, 20});
|
||||
|
||||
// Can take address
|
||||
Point *p = &(Point){5, 15};
|
||||
printf("(%d, %d)\n", p->x, p->y);
|
||||
|
||||
// Array compound literal
|
||||
int *arr = (int[]){1, 2, 3, 4, 5};
|
||||
printf("%d\n", arr[0]); // 1
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Compound Literals in Macros
|
||||
|
||||
```c
|
||||
#define POINT(x, y) ((Point){x, y})
|
||||
#define COLOR(r, g, b, a) ((Color){r, g, b, a})
|
||||
|
||||
typedef struct {
|
||||
float r, g, b, a;
|
||||
} Color;
|
||||
|
||||
int main(void) {
|
||||
Point p1 = POINT(10, 20);
|
||||
Color red = COLOR(255, 0, 0, 255);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**Clay Example** (from clay.h):
|
||||
```c
|
||||
// Clay uses compound literals extensively
|
||||
#define CLAY__INIT(type) (type)
|
||||
|
||||
#define CLAY_COLOR(r, g, b, a) \
|
||||
CLAY__INIT(Clay_Color){.r = r, .g = g, .b = b, .a = a}
|
||||
|
||||
#define CLAY_DIMENSIONS(w, h) \
|
||||
CLAY__INIT(Clay_Dimensions){.width = w, .height = h}
|
||||
|
||||
// Usage
|
||||
Clay_Color red = CLAY_COLOR(255, 0, 0, 255);
|
||||
Clay_Dimensions size = CLAY_DIMENSIONS(100, 50);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16.5 Statement Expressions (GCC Extension)
|
||||
|
||||
Create macros that return values safely.
|
||||
|
||||
### Basic Statement Expression
|
||||
|
||||
```c
|
||||
#define MAX(a, b) ({ \
|
||||
typeof(a) _a = (a); \
|
||||
typeof(b) _b = (b); \
|
||||
_a > _b ? _a : _b; \
|
||||
})
|
||||
|
||||
int main(void) {
|
||||
int x = 5, y = 10;
|
||||
|
||||
// Safe: expressions evaluated once
|
||||
int max = MAX(x++, y++);
|
||||
printf("max=%d, x=%d, y=%d\n", max, x, y);
|
||||
// max=10, x=6, y=11 (each incremented once)
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Complex Statement Expression
|
||||
|
||||
```c
|
||||
#define SWAP(a, b) ({ \
|
||||
typeof(a) _temp = a; \
|
||||
a = b; \
|
||||
b = _temp; \
|
||||
_temp; \
|
||||
})
|
||||
|
||||
int main(void) {
|
||||
int x = 10, y = 20;
|
||||
int old_x = SWAP(x, y);
|
||||
|
||||
printf("x=%d, y=%d, old_x=%d\n", x, y, old_x);
|
||||
// x=20, y=10, old_x=10
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** Statement expressions are GCC/Clang extension, not standard C.
|
||||
|
||||
---
|
||||
|
||||
## 16.6 For-Loop Macro Trick
|
||||
|
||||
Create scope-based macros with automatic cleanup.
|
||||
|
||||
### Basic For-Loop Trick
|
||||
|
||||
```c
|
||||
#define WITH_LOCK(mutex) \
|
||||
for (int _i = (lock(mutex), 0); \
|
||||
_i < 1; \
|
||||
_i++, unlock(mutex))
|
||||
|
||||
// Usage
|
||||
WITH_LOCK(&myMutex) {
|
||||
// Critical section
|
||||
// mutex automatically unlocked when block exits
|
||||
}
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
```c
|
||||
for (init; condition; increment) {
|
||||
body
|
||||
}
|
||||
|
||||
// 1. init runs once: lock mutex, set _i = 0
|
||||
// 2. condition checked: _i < 1 is true (0 < 1)
|
||||
// 3. body executes
|
||||
// 4. increment runs: _i++, unlock mutex
|
||||
// 5. condition checked: _i < 1 is false (1 < 1)
|
||||
// 6. loop exits
|
||||
```
|
||||
|
||||
**Clay Example** (from clay.h:2016):
|
||||
```c
|
||||
// Clay's famous CLAY() macro
|
||||
#define CLAY(...) \
|
||||
for (CLAY__ELEMENT_DEFINITION_LATCH = ( \
|
||||
Clay__OpenElement(), \
|
||||
Clay__ConfigureOpenElement(__VA_ARGS__), \
|
||||
0 \
|
||||
); \
|
||||
CLAY__ELEMENT_DEFINITION_LATCH < 1; \
|
||||
CLAY__ELEMENT_DEFINITION_LATCH = 1, Clay__CloseElement())
|
||||
|
||||
// Usage: automatically opens and closes elements
|
||||
CLAY(CLAY_ID("Button"), CLAY_LAYOUT(...)) {
|
||||
CLAY_TEXT(CLAY_STRING("Click me"), ...);
|
||||
}
|
||||
// Clay__CloseElement() called automatically
|
||||
```
|
||||
|
||||
### Step-by-Step Breakdown
|
||||
|
||||
```c
|
||||
// This code:
|
||||
CLAY(config) {
|
||||
// children
|
||||
}
|
||||
|
||||
// Expands to:
|
||||
for (CLAY__ELEMENT_DEFINITION_LATCH = (
|
||||
Clay__OpenElement(), // 1. Open element
|
||||
Clay__ConfigureOpenElement(config), // 2. Configure
|
||||
0 // 3. Set latch to 0
|
||||
);
|
||||
CLAY__ELEMENT_DEFINITION_LATCH < 1; // 4. Check: 0 < 1 = true, enter loop
|
||||
CLAY__ELEMENT_DEFINITION_LATCH = 1, Clay__CloseElement()) // 7. Set latch=1, close
|
||||
{
|
||||
// 5. User code runs (children)
|
||||
}
|
||||
// 6. Back to increment
|
||||
// 8. Check condition: 1 < 1 = false, exit loop
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16.7 Designated Initializers in Macros
|
||||
|
||||
C99 feature for clean initialization.
|
||||
|
||||
### Basic Designated Initializers
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
int x, y, z;
|
||||
char *name;
|
||||
} Data;
|
||||
|
||||
int main(void) {
|
||||
// Order doesn't matter
|
||||
Data d = {
|
||||
.z = 30,
|
||||
.name = "test",
|
||||
.x = 10,
|
||||
.y = 20
|
||||
};
|
||||
|
||||
// Partial initialization (rest = 0/NULL)
|
||||
Data d2 = {.x = 5};
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Macros with Designated Initializers
|
||||
|
||||
```c
|
||||
#define CREATE_POINT(x, y) \
|
||||
(Point){.x = (x), .y = (y)}
|
||||
|
||||
#define CREATE_COLOR(r, g, b) \
|
||||
(Color){.r = (r), .g = (g), .b = (b), .a = 255}
|
||||
|
||||
int main(void) {
|
||||
Point p = CREATE_POINT(10, 20);
|
||||
Color c = CREATE_COLOR(255, 0, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**Clay Example** (from clay.h):
|
||||
```c
|
||||
// Clean API with designated initializers
|
||||
#define CLAY_LAYOUT(...) \
|
||||
(Clay_LayoutConfig){__VA_ARGS__}
|
||||
|
||||
#define CLAY_SIZING_FIT(min, max) \
|
||||
(Clay_Sizing){ \
|
||||
.type = CLAY_SIZING_TYPE_FIT, \
|
||||
.size = {.minMax = {.min = min, .max = max}} \
|
||||
}
|
||||
|
||||
// Usage
|
||||
Clay_LayoutConfig layout = CLAY_LAYOUT(
|
||||
.sizing = {
|
||||
.width = CLAY_SIZING_FIT(100, 500),
|
||||
.height = CLAY_SIZING_GROW(0)
|
||||
},
|
||||
.padding = CLAY_PADDING_ALL(16),
|
||||
.childGap = 8
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16.8 Recursive Macros
|
||||
|
||||
Macros that appear to call themselves (with limitations).
|
||||
|
||||
### Deferred Expression
|
||||
|
||||
```c
|
||||
#define EMPTY()
|
||||
#define DEFER(x) x EMPTY()
|
||||
#define EXPAND(...) __VA_ARGS__
|
||||
|
||||
#define A() 123
|
||||
#define B() A()
|
||||
|
||||
// Direct expansion
|
||||
int x = B(); // Expands to: A()
|
||||
|
||||
// Deferred expansion
|
||||
int y = EXPAND(DEFER(B)()); // Expands to: 123
|
||||
```
|
||||
|
||||
### Recursive List Processing
|
||||
|
||||
```c
|
||||
// Limited recursion using deferred expansion
|
||||
#define EVAL(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
|
||||
#define EVAL1(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
|
||||
#define EVAL2(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
|
||||
#define EVAL3(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
|
||||
#define EVAL4(...) __VA_ARGS__
|
||||
|
||||
#define EMPTY()
|
||||
#define DEFER(x) x EMPTY()
|
||||
#define OBSTRUCT(x) x DEFER(EMPTY)()
|
||||
|
||||
#define REPEAT(count, macro, ...) \
|
||||
REPEAT_IMPL(count, macro, __VA_ARGS__)
|
||||
|
||||
// Complex implementation...
|
||||
```
|
||||
|
||||
**Note:** True recursion is not possible in standard C preprocessor.
|
||||
|
||||
---
|
||||
|
||||
## 16.9 Macro Debugging
|
||||
|
||||
### Viewing Macro Expansion
|
||||
|
||||
```bash
|
||||
# GCC/Clang: preprocess only
|
||||
gcc -E program.c
|
||||
gcc -E program.c | grep "main" # Filter output
|
||||
|
||||
# Save preprocessed output
|
||||
gcc -E program.c -o program.i
|
||||
|
||||
# With line markers
|
||||
gcc -E -P program.c # Remove line markers
|
||||
```
|
||||
|
||||
### Debug Macros
|
||||
|
||||
```c
|
||||
// Print macro expansion
|
||||
#define SHOW(x) #x
|
||||
#define EXPAND_SHOW(x) SHOW(x)
|
||||
|
||||
#define MY_MACRO(a, b) ((a) + (b))
|
||||
|
||||
#pragma message "MY_MACRO(1,2) = " EXPAND_SHOW(MY_MACRO(1, 2))
|
||||
// Output: MY_MACRO(1,2) = ((1) + (2))
|
||||
```
|
||||
|
||||
### Compile-Time Assertions
|
||||
|
||||
```c
|
||||
// Static assertion
|
||||
#define BUILD_BUG_ON(condition) \
|
||||
((void)sizeof(char[1 - 2*!!(condition)]))
|
||||
|
||||
// C11 static_assert
|
||||
_Static_assert(sizeof(int) == 4, "int must be 4 bytes");
|
||||
```
|
||||
|
||||
**Clay Example:**
|
||||
```c
|
||||
// Clay uses assertions for debugging
|
||||
#define CLAY__ASSERT(condition) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
Clay__ErrorHandler(...); \
|
||||
} \
|
||||
} while(0)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16.10 Advanced Clay Macro Techniques
|
||||
|
||||
### Element Configuration
|
||||
|
||||
```c
|
||||
// Multiple configuration macros
|
||||
CLAY(
|
||||
CLAY_ID("Panel"),
|
||||
CLAY_LAYOUT({
|
||||
.sizing = {
|
||||
.width = CLAY_SIZING_GROW(0),
|
||||
.height = CLAY_SIZING_FIXED(200)
|
||||
},
|
||||
.padding = CLAY_PADDING_ALL(16),
|
||||
.childGap = 8
|
||||
}),
|
||||
CLAY_RECTANGLE_CONFIG({
|
||||
.color = CLAY_COLOR(200, 200, 200, 255),
|
||||
.cornerRadius = CLAY_CORNER_RADIUS_ALL(8)
|
||||
}),
|
||||
CLAY_BORDER_CONFIG({
|
||||
.width = 2,
|
||||
.color = CLAY_COLOR(100, 100, 100, 255)
|
||||
})
|
||||
) {
|
||||
// Children
|
||||
}
|
||||
```
|
||||
|
||||
### ID Generation
|
||||
|
||||
```c
|
||||
// String-based IDs
|
||||
#define CLAY_ID(label) \
|
||||
Clay__HashString(CLAY_STRING(label), 0, 0)
|
||||
|
||||
// Indexed IDs for lists
|
||||
#define CLAY_IDI(label, index) \
|
||||
Clay__HashString(CLAY_STRING(label), index, 0)
|
||||
|
||||
// Usage
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
CLAY(CLAY_IDI("ListItem", i)) {
|
||||
// Item content
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Text Elements
|
||||
|
||||
```c
|
||||
// Macro for text with configuration
|
||||
#define CLAY_TEXT(text, config) \
|
||||
Clay__OpenTextElement(text, config)
|
||||
|
||||
// Usage
|
||||
CLAY_TEXT(
|
||||
CLAY_STRING("Hello World"),
|
||||
CLAY_TEXT_CONFIG({
|
||||
.fontSize = 24,
|
||||
.textColor = CLAY_COLOR(0, 0, 0, 255)
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16.11 Complete Advanced Example
|
||||
|
||||
```c
|
||||
#define CLAY_IMPLEMENTATION
|
||||
#include "clay.h"
|
||||
|
||||
// Custom button macro
|
||||
#define UI_BUTTON(id, text, onClick) \
|
||||
CLAY( \
|
||||
CLAY_ID(id), \
|
||||
CLAY_LAYOUT({ \
|
||||
.padding = CLAY_PADDING_ALL(12), \
|
||||
.sizing = { \
|
||||
.width = CLAY_SIZING_FIT(0, 0) \
|
||||
} \
|
||||
}), \
|
||||
CLAY_RECTANGLE_CONFIG({ \
|
||||
.color = CLAY_COLOR(70, 130, 180, 255), \
|
||||
.cornerRadius = CLAY_CORNER_RADIUS_ALL(4) \
|
||||
}) \
|
||||
) { \
|
||||
CLAY_TEXT( \
|
||||
CLAY_STRING(text), \
|
||||
CLAY_TEXT_CONFIG({ \
|
||||
.fontSize = 16, \
|
||||
.textColor = CLAY_COLOR(255, 255, 255, 255) \
|
||||
}) \
|
||||
); \
|
||||
}
|
||||
|
||||
// Custom panel macro
|
||||
#define UI_PANEL(id, ...) \
|
||||
CLAY( \
|
||||
CLAY_ID(id), \
|
||||
CLAY_LAYOUT({ \
|
||||
.padding = CLAY_PADDING_ALL(16), \
|
||||
.childGap = 8, \
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM \
|
||||
}), \
|
||||
CLAY_RECTANGLE_CONFIG({ \
|
||||
.color = CLAY_COLOR(240, 240, 240, 255) \
|
||||
}) \
|
||||
)
|
||||
|
||||
int main(void) {
|
||||
Clay_BeginLayout();
|
||||
|
||||
UI_PANEL("MainPanel") {
|
||||
CLAY_TEXT(
|
||||
CLAY_STRING("Welcome"),
|
||||
CLAY_TEXT_CONFIG({.fontSize = 32})
|
||||
);
|
||||
|
||||
UI_BUTTON("SubmitBtn", "Submit", handleSubmit);
|
||||
UI_BUTTON("CancelBtn", "Cancel", handleCancel);
|
||||
}
|
||||
|
||||
Clay_RenderCommandArray commands = Clay_EndLayout();
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16.12 Key Concepts Learned
|
||||
|
||||
- ✅ Variadic macros with __VA_ARGS__
|
||||
- ✅ X-Macros for code generation
|
||||
- ✅ _Generic for type-based selection
|
||||
- ✅ Compound literals for temporary values
|
||||
- ✅ Statement expressions (GCC)
|
||||
- ✅ For-loop macro trick for scope
|
||||
- ✅ Designated initializers
|
||||
- ✅ Recursive macro techniques
|
||||
- ✅ Macro debugging methods
|
||||
- ✅ Clay's advanced macro patterns
|
||||
|
||||
---
|
||||
|
||||
## Practice Exercises
|
||||
|
||||
1. Create a logging system with different log levels using variadic macros
|
||||
2. Implement a state machine using X-Macros
|
||||
3. Build a type-safe print function using _Generic
|
||||
4. Design a resource manager with for-loop cleanup macros
|
||||
5. Create a unit testing framework using macros
|
||||
6. Implement a configurable array type with macros
|
||||
7. Build a simple OOP system with macros
|
||||
8. Create a domain-specific language using Clay-style macros
|
||||
|
||||
812
docs/17_memory_management.md
Normal file
812
docs/17_memory_management.md
Normal file
|
|
@ -0,0 +1,812 @@
|
|||
# Chapter 17: Memory Management in C
|
||||
|
||||
## Complete Guide with Clay Library Examples
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
1. Stack vs Heap Memory
|
||||
2. malloc, calloc, realloc, free
|
||||
3. Common Memory Errors
|
||||
4. Memory Leaks and Detection
|
||||
5. Arena Allocators
|
||||
6. Memory Pools
|
||||
7. Memory Alignment
|
||||
8. Custom Allocators
|
||||
9. Clay's Memory Strategy
|
||||
10. Best Practices
|
||||
|
||||
---
|
||||
|
||||
## 17.1 Stack vs Heap Memory
|
||||
|
||||
### Stack Memory
|
||||
|
||||
**Characteristics:**
|
||||
- ✅ Automatic allocation/deallocation
|
||||
- ✅ Very fast (just moving stack pointer)
|
||||
- ✅ Limited size (typically 1-8 MB)
|
||||
- ✅ LIFO (Last In, First Out)
|
||||
- ✅ Variables destroyed when function returns
|
||||
|
||||
```c
|
||||
void function(void) {
|
||||
int x = 10; // Stack
|
||||
char buffer[100]; // Stack
|
||||
float arr[50]; // Stack
|
||||
} // All memory automatically freed here
|
||||
```
|
||||
|
||||
### Heap Memory
|
||||
|
||||
**Characteristics:**
|
||||
- Manual allocation with malloc/calloc
|
||||
- Manual deallocation with free
|
||||
- Slower than stack
|
||||
- Large size available (GBs)
|
||||
- Persists until explicitly freed
|
||||
- Risk of memory leaks
|
||||
|
||||
```c
|
||||
#include <stdlib.h>
|
||||
|
||||
void function(void) {
|
||||
int *p = malloc(sizeof(int)); // Heap
|
||||
*p = 10;
|
||||
// ...
|
||||
free(p); // Must manually free!
|
||||
}
|
||||
```
|
||||
|
||||
### Comparison
|
||||
|
||||
```c
|
||||
void stackExample(void) {
|
||||
int arr[1000]; // Stack: fast, automatic cleanup
|
||||
// Use arr
|
||||
} // Automatically freed
|
||||
|
||||
void heapExample(void) {
|
||||
int *arr = malloc(1000 * sizeof(int)); // Heap: manual management
|
||||
if (arr == NULL) {
|
||||
// Handle error
|
||||
return;
|
||||
}
|
||||
// Use arr
|
||||
free(arr); // Must free manually
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17.2 malloc, calloc, realloc, free
|
||||
|
||||
### malloc - Allocate Memory
|
||||
|
||||
```c
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(void) {
|
||||
// Allocate 10 integers
|
||||
int *arr = (int*)malloc(10 * sizeof(int));
|
||||
|
||||
if (arr == NULL) {
|
||||
printf("Memory allocation failed!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Memory is UNINITIALIZED (contains garbage)
|
||||
for (int i = 0; i < 10; i++) {
|
||||
arr[i] = i * 2;
|
||||
}
|
||||
|
||||
free(arr); // Always free!
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### calloc - Allocate and Zero
|
||||
|
||||
```c
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(void) {
|
||||
// Allocate 10 integers, initialized to 0
|
||||
int *arr = (int*)calloc(10, sizeof(int));
|
||||
|
||||
if (arr == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// All elements are 0
|
||||
for (int i = 0; i < 10; i++) {
|
||||
printf("%d ", arr[i]); // 0 0 0 0 0 0 0 0 0 0
|
||||
}
|
||||
|
||||
free(arr);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**malloc vs calloc:**
|
||||
- `malloc(n)`: Allocates n bytes, uninitialized
|
||||
- `calloc(count, size)`: Allocates count*size bytes, zero-initialized
|
||||
|
||||
### realloc - Resize Memory
|
||||
|
||||
```c
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(void) {
|
||||
// Allocate 10 integers
|
||||
int *arr = malloc(10 * sizeof(int));
|
||||
if (arr == NULL) return 1;
|
||||
|
||||
// Initialize
|
||||
for (int i = 0; i < 10; i++) {
|
||||
arr[i] = i;
|
||||
}
|
||||
|
||||
// Need more space - resize to 20 integers
|
||||
int *temp = realloc(arr, 20 * sizeof(int));
|
||||
if (temp == NULL) {
|
||||
// Realloc failed, original arr still valid
|
||||
free(arr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
arr = temp; // Update pointer
|
||||
// First 10 elements preserved, last 10 uninitialized
|
||||
|
||||
free(arr);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**realloc behavior:**
|
||||
- May move data to new location
|
||||
- Original contents preserved up to old size
|
||||
- Returns NULL on failure (old pointer still valid)
|
||||
- Can shrink or grow allocation
|
||||
|
||||
### free - Deallocate Memory
|
||||
|
||||
```c
|
||||
int main(void) {
|
||||
int *p = malloc(sizeof(int));
|
||||
|
||||
if (p != NULL) {
|
||||
*p = 42;
|
||||
// Use p
|
||||
free(p); // Release memory
|
||||
p = NULL; // Good practice: prevent dangling pointer
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17.3 Common Memory Errors
|
||||
|
||||
### 1. Memory Leak
|
||||
|
||||
```c
|
||||
void leak(void) {
|
||||
int *p = malloc(sizeof(int));
|
||||
*p = 42;
|
||||
// Forgot to free(p)!
|
||||
} // Memory leaked!
|
||||
|
||||
int main(void) {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
leak(); // Leaks 4 bytes per iteration = 4KB total
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Use After Free
|
||||
|
||||
```c
|
||||
int main(void) {
|
||||
int *p = malloc(sizeof(int));
|
||||
*p = 42;
|
||||
|
||||
free(p);
|
||||
|
||||
*p = 100; // UNDEFINED BEHAVIOR! Memory no longer owned
|
||||
printf("%d\n", *p); // May crash or print garbage
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Double Free
|
||||
|
||||
```c
|
||||
int main(void) {
|
||||
int *p = malloc(sizeof(int));
|
||||
|
||||
free(p);
|
||||
free(p); // CRASH! Double free
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Invalid Free
|
||||
|
||||
```c
|
||||
int main(void) {
|
||||
int x = 10;
|
||||
int *p = &x; // Points to stack variable
|
||||
|
||||
free(p); // CRASH! Can only free heap memory
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Buffer Overflow
|
||||
|
||||
```c
|
||||
int main(void) {
|
||||
int *arr = malloc(10 * sizeof(int));
|
||||
|
||||
arr[15] = 100; // OUT OF BOUNDS! Undefined behavior
|
||||
|
||||
free(arr);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Uninitialized Memory
|
||||
|
||||
```c
|
||||
int main(void) {
|
||||
int *p = malloc(sizeof(int));
|
||||
printf("%d\n", *p); // Garbage value!
|
||||
|
||||
free(p);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17.4 Memory Leaks and Detection
|
||||
|
||||
### Detecting Leaks with Valgrind
|
||||
|
||||
```bash
|
||||
# Compile with debug symbols
|
||||
gcc -g program.c -o program
|
||||
|
||||
# Run with Valgrind
|
||||
valgrind --leak-check=full ./program
|
||||
```
|
||||
|
||||
**Output example:**
|
||||
```
|
||||
HEAP SUMMARY:
|
||||
in use at exit: 40 bytes in 1 blocks
|
||||
total heap usage: 2 allocs, 1 frees, 1,064 bytes allocated
|
||||
|
||||
40 bytes in 1 blocks are definitely lost in loss record 1 of 1
|
||||
at 0x: malloc
|
||||
by 0x: main (program.c:10)
|
||||
```
|
||||
|
||||
### Manual Leak Tracking
|
||||
|
||||
```c
|
||||
#ifdef DEBUG
|
||||
static size_t allocations = 0;
|
||||
static size_t deallocations = 0;
|
||||
|
||||
#define MALLOC(size) \
|
||||
(allocations++, malloc(size))
|
||||
|
||||
#define FREE(ptr) \
|
||||
(deallocations++, free(ptr))
|
||||
|
||||
#define REPORT_LEAKS() \
|
||||
printf("Allocations: %zu, Frees: %zu, Leaked: %zu\n", \
|
||||
allocations, deallocations, allocations - deallocations)
|
||||
#else
|
||||
#define MALLOC(size) malloc(size)
|
||||
#define FREE(ptr) free(ptr)
|
||||
#define REPORT_LEAKS()
|
||||
#endif
|
||||
|
||||
int main(void) {
|
||||
int *p1 = MALLOC(sizeof(int));
|
||||
int *p2 = MALLOC(sizeof(int));
|
||||
|
||||
FREE(p1);
|
||||
// Forgot to free p2!
|
||||
|
||||
REPORT_LEAKS(); // Shows 1 leak
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17.5 Arena Allocators
|
||||
|
||||
Allocate from a pre-allocated buffer - fast and predictable.
|
||||
|
||||
### Basic Arena
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
char *memory;
|
||||
size_t size;
|
||||
size_t used;
|
||||
} Arena;
|
||||
|
||||
void Arena_Init(Arena *arena, size_t size) {
|
||||
arena->memory = malloc(size);
|
||||
arena->size = size;
|
||||
arena->used = 0;
|
||||
}
|
||||
|
||||
void* Arena_Alloc(Arena *arena, size_t size) {
|
||||
// Align to 8 bytes
|
||||
size = (size + 7) & ~7;
|
||||
|
||||
if (arena->used + size > arena->size) {
|
||||
return NULL; // Out of memory
|
||||
}
|
||||
|
||||
void *ptr = arena->memory + arena->used;
|
||||
arena->used += size;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void Arena_Free(Arena *arena) {
|
||||
free(arena->memory); // Free everything at once
|
||||
arena->used = 0;
|
||||
}
|
||||
|
||||
void Arena_Reset(Arena *arena) {
|
||||
arena->used = 0; // Reset without freeing
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
Arena arena;
|
||||
Arena_Init(&arena, 1024 * 1024); // 1MB
|
||||
|
||||
// Allocate from arena
|
||||
int *arr1 = Arena_Alloc(&arena, 100 * sizeof(int));
|
||||
float *arr2 = Arena_Alloc(&arena, 50 * sizeof(float));
|
||||
|
||||
// Use allocations...
|
||||
|
||||
// Free everything at once
|
||||
Arena_Free(&arena);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**Clay Example** (from clay.h:185):
|
||||
```c
|
||||
typedef struct {
|
||||
uintptr_t nextAllocation;
|
||||
size_t capacity;
|
||||
char *memory;
|
||||
} Clay_Arena;
|
||||
|
||||
void* Clay__Array_Allocate_Arena(
|
||||
int32_t capacity,
|
||||
uint32_t itemSize,
|
||||
Clay_Arena *arena
|
||||
) {
|
||||
size_t totalSizeBytes = capacity * itemSize;
|
||||
uintptr_t nextAllocation = arena->nextAllocation + totalSizeBytes;
|
||||
|
||||
if (nextAllocation <= arena->capacity) {
|
||||
void *allocation = (void*)(arena->memory + arena->nextAllocation);
|
||||
arena->nextAllocation = nextAllocation;
|
||||
return allocation;
|
||||
}
|
||||
|
||||
return NULL; // Out of memory
|
||||
}
|
||||
|
||||
// Clay initializes arena once
|
||||
Clay_Arena arena = {
|
||||
.nextAllocation = 0,
|
||||
.capacity = CLAY_MAX_ELEMENT_COUNT * sizeof(Clay_LayoutElement),
|
||||
.memory = arenaMemory
|
||||
};
|
||||
|
||||
// All allocations from arena - no malloc in hot path!
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ✅ Very fast allocation (just increment pointer)
|
||||
- ✅ No fragmentation
|
||||
- ✅ Bulk deallocation (free everything at once)
|
||||
- ✅ Cache-friendly (linear memory)
|
||||
- ✅ Predictable memory usage
|
||||
|
||||
---
|
||||
|
||||
## 17.6 Memory Pools
|
||||
|
||||
Pre-allocate objects of same size.
|
||||
|
||||
### Basic Pool
|
||||
|
||||
```c
|
||||
#define POOL_SIZE 100
|
||||
|
||||
typedef struct Node {
|
||||
int value;
|
||||
struct Node *next;
|
||||
} Node;
|
||||
|
||||
typedef struct {
|
||||
Node nodes[POOL_SIZE];
|
||||
Node *freeList;
|
||||
int allocated;
|
||||
} NodePool;
|
||||
|
||||
void Pool_Init(NodePool *pool) {
|
||||
// Chain all nodes into free list
|
||||
for (int i = 0; i < POOL_SIZE - 1; i++) {
|
||||
pool->nodes[i].next = &pool->nodes[i + 1];
|
||||
}
|
||||
pool->nodes[POOL_SIZE - 1].next = NULL;
|
||||
pool->freeList = &pool->nodes[0];
|
||||
pool->allocated = 0;
|
||||
}
|
||||
|
||||
Node* Pool_Alloc(NodePool *pool) {
|
||||
if (pool->freeList == NULL) {
|
||||
return NULL; // Pool exhausted
|
||||
}
|
||||
|
||||
Node *node = pool->freeList;
|
||||
pool->freeList = node->next;
|
||||
pool->allocated++;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
void Pool_Free(NodePool *pool, Node *node) {
|
||||
node->next = pool->freeList;
|
||||
pool->freeList = node;
|
||||
pool->allocated--;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
NodePool pool;
|
||||
Pool_Init(&pool);
|
||||
|
||||
// Allocate nodes
|
||||
Node *n1 = Pool_Alloc(&pool);
|
||||
Node *n2 = Pool_Alloc(&pool);
|
||||
n1->value = 10;
|
||||
n2->value = 20;
|
||||
|
||||
// Free nodes
|
||||
Pool_Free(&pool, n1);
|
||||
Pool_Free(&pool, n2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ✅ Constant-time allocation/deallocation
|
||||
- ✅ No fragmentation
|
||||
- ✅ Good cache locality
|
||||
- ✅ Predictable performance
|
||||
|
||||
---
|
||||
|
||||
## 17.7 Memory Alignment
|
||||
|
||||
CPUs prefer aligned memory access.
|
||||
|
||||
### Alignment Basics
|
||||
|
||||
```c
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct {
|
||||
char a; // 1 byte
|
||||
// 3 bytes padding
|
||||
int b; // 4 bytes (aligned to 4)
|
||||
char c; // 1 byte
|
||||
// 3 bytes padding
|
||||
} Unaligned; // Total: 12 bytes
|
||||
|
||||
typedef struct {
|
||||
int b; // 4 bytes
|
||||
char a; // 1 byte
|
||||
char c; // 1 byte
|
||||
// 2 bytes padding
|
||||
} Optimized; // Total: 8 bytes
|
||||
|
||||
int main(void) {
|
||||
printf("Unaligned: %zu\n", sizeof(Unaligned)); // 12
|
||||
printf("Optimized: %zu\n", sizeof(Optimized)); // 8
|
||||
|
||||
printf("Alignment of int: %zu\n", _Alignof(int)); // 4
|
||||
printf("Alignment of double: %zu\n", _Alignof(double)); // 8
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Manual Alignment
|
||||
|
||||
```c
|
||||
// Align size to power of 2
|
||||
size_t alignSize(size_t size, size_t alignment) {
|
||||
return (size + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
size_t size = 25;
|
||||
|
||||
printf("Align to 8: %zu\n", alignSize(size, 8)); // 32
|
||||
printf("Align to 16: %zu\n", alignSize(size, 16)); // 32
|
||||
printf("Align to 32: %zu\n", alignSize(size, 32)); // 32
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### aligned_alloc (C11)
|
||||
|
||||
```c
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(void) {
|
||||
// Allocate 1024 bytes aligned to 64-byte boundary
|
||||
void *p = aligned_alloc(64, 1024);
|
||||
|
||||
if (p != NULL) {
|
||||
// p is guaranteed to be 64-byte aligned
|
||||
free(p);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**Clay Example:**
|
||||
```c
|
||||
// Clay carefully orders struct members for optimal alignment
|
||||
typedef struct {
|
||||
float width, height; // 8 bytes total, 4-byte aligned
|
||||
} Clay_Dimensions;
|
||||
|
||||
typedef struct {
|
||||
Clay_Vector2 x, y; // 16 bytes total, 4-byte aligned
|
||||
} Clay_BoundingBox;
|
||||
|
||||
// Compact and cache-friendly!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17.8 Custom Allocators
|
||||
|
||||
Create application-specific allocators.
|
||||
|
||||
### Debug Allocator
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
void *ptr;
|
||||
size_t size;
|
||||
const char *file;
|
||||
int line;
|
||||
} AllocationInfo;
|
||||
|
||||
#define MAX_ALLOCATIONS 1000
|
||||
AllocationInfo allocations[MAX_ALLOCATIONS];
|
||||
int allocationCount = 0;
|
||||
|
||||
void* debug_malloc(size_t size, const char *file, int line) {
|
||||
void *ptr = malloc(size);
|
||||
|
||||
if (ptr != NULL && allocationCount < MAX_ALLOCATIONS) {
|
||||
allocations[allocationCount++] = (AllocationInfo){
|
||||
.ptr = ptr,
|
||||
.size = size,
|
||||
.file = file,
|
||||
.line = line
|
||||
};
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void debug_free(void *ptr) {
|
||||
// Remove from tracking
|
||||
for (int i = 0; i < allocationCount; i++) {
|
||||
if (allocations[i].ptr == ptr) {
|
||||
allocations[i] = allocations[--allocationCount];
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
void report_leaks(void) {
|
||||
printf("Memory leaks: %d allocations\n", allocationCount);
|
||||
for (int i = 0; i < allocationCount; i++) {
|
||||
printf(" %zu bytes at %s:%d\n",
|
||||
allocations[i].size,
|
||||
allocations[i].file,
|
||||
allocations[i].line);
|
||||
}
|
||||
}
|
||||
|
||||
#define MALLOC(size) debug_malloc(size, __FILE__, __LINE__)
|
||||
#define FREE(ptr) debug_free(ptr)
|
||||
```
|
||||
|
||||
### Scratch Allocator
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
char buffer[4096];
|
||||
size_t used;
|
||||
} ScratchAllocator;
|
||||
|
||||
ScratchAllocator scratch = {0};
|
||||
|
||||
void* scratch_alloc(size_t size) {
|
||||
if (scratch.used + size > sizeof(scratch.buffer)) {
|
||||
return NULL;
|
||||
}
|
||||
void *ptr = scratch.buffer + scratch.used;
|
||||
scratch.used += size;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void scratch_reset(void) {
|
||||
scratch.used = 0;
|
||||
}
|
||||
|
||||
// Use for temporary allocations
|
||||
void process_frame(void) {
|
||||
char *temp = scratch_alloc(1000);
|
||||
// Use temp
|
||||
scratch_reset(); // Free everything
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17.9 Clay's Memory Strategy
|
||||
|
||||
**Key principles:**
|
||||
|
||||
1. **Arena Allocation**: All memory from pre-allocated arenas
|
||||
2. **No malloc in Hot Path**: Allocations done at initialization
|
||||
3. **Predictable Memory**: Know exactly how much is needed
|
||||
4. **Cache-Friendly**: Linear memory layout
|
||||
|
||||
```c
|
||||
// User provides memory
|
||||
char arenaMemory[CLAY_MAX_ELEMENT_COUNT * sizeof(Clay_LayoutElement)];
|
||||
|
||||
Clay_Arena arena = {
|
||||
.nextAllocation = 0,
|
||||
.capacity = sizeof(arenaMemory),
|
||||
.memory = arenaMemory
|
||||
};
|
||||
|
||||
// Initialize Clay with arena
|
||||
Clay_Initialize(arena, screenSize);
|
||||
|
||||
// Layout calculation uses only arena memory
|
||||
Clay_BeginLayout();
|
||||
// ... build UI
|
||||
Clay_EndLayout(); // No allocations, just arena usage
|
||||
|
||||
// Can reset arena each frame if needed
|
||||
arena.nextAllocation = 0;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17.10 Best Practices
|
||||
|
||||
### 1. Always Check malloc Return
|
||||
|
||||
```c
|
||||
int *p = malloc(sizeof(int));
|
||||
if (p == NULL) {
|
||||
// Handle error!
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Free What You Allocate
|
||||
|
||||
```c
|
||||
void function(void) {
|
||||
int *p = malloc(sizeof(int));
|
||||
// Use p
|
||||
free(p); // Always free!
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Set Pointers to NULL After Free
|
||||
|
||||
```c
|
||||
free(p);
|
||||
p = NULL; // Prevent use-after-free
|
||||
```
|
||||
|
||||
### 4. Use sizeof on Variables
|
||||
|
||||
```c
|
||||
int *p = malloc(sizeof(*p)); // Safer than sizeof(int)
|
||||
```
|
||||
|
||||
### 5. Consider Arena Allocators
|
||||
|
||||
```c
|
||||
// Instead of many small allocations
|
||||
int *a = malloc(sizeof(int));
|
||||
int *b = malloc(sizeof(int));
|
||||
// ...lots of malloc/free
|
||||
|
||||
// Use arena for related allocations
|
||||
Arena arena;
|
||||
Arena_Init(&arena, 1024);
|
||||
int *a = Arena_Alloc(&arena, sizeof(int));
|
||||
int *b = Arena_Alloc(&arena, sizeof(int));
|
||||
// Free all at once
|
||||
Arena_Free(&arena);
|
||||
```
|
||||
|
||||
### 6. Profile Memory Usage
|
||||
|
||||
```bash
|
||||
valgrind --tool=massif ./program
|
||||
ms_print massif.out.12345
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17.11 Key Concepts Learned
|
||||
|
||||
- ✅ Stack vs heap memory
|
||||
- ✅ malloc, calloc, realloc, free
|
||||
- ✅ Common memory errors and prevention
|
||||
- ✅ Memory leak detection
|
||||
- ✅ Arena allocators for fast bulk allocation
|
||||
- ✅ Memory pools for same-sized objects
|
||||
- ✅ Memory alignment and optimization
|
||||
- ✅ Custom allocators
|
||||
- ✅ Clay's efficient memory strategy
|
||||
- ✅ Best practices for memory management
|
||||
|
||||
---
|
||||
|
||||
## Practice Exercises
|
||||
|
||||
1. Implement a dynamic array that grows automatically
|
||||
2. Create a garbage collector using reference counting
|
||||
3. Build a memory debugger that tracks all allocations
|
||||
4. Implement a slab allocator for kernel-style allocation
|
||||
5. Create a stack allocator with save/restore points
|
||||
6. Build a ring buffer allocator
|
||||
7. Implement buddy allocation system
|
||||
8. Create thread-safe memory pool
|
||||
|
||||
Loading…
Reference in a new issue