clay/LEARNING_C_WITH_CLAY.md
Claude f31d64023c
Add comprehensive C learning guide using Clay library
- Complete step-by-step tutorial covering all major C concepts
- 13 chapters from basics to advanced topics
- Real-world examples from Clay library throughout
- Topics include: variables, functions, pointers, structs, arrays,
  memory management, macros, header files, enums, unions, and more
- Practical exercises and next steps for learners
2025-11-13 20:13:43 +00:00

51 KiB

Learning C Programming with Clay: A Complete Step-by-Step Guide

Table of Contents

  1. Introduction to C and Clay
  2. Chapter 1: C Basics - Your First Program
  3. Chapter 2: Variables and Data Types
  4. Chapter 3: Functions
  5. Chapter 4: Pointers - The Heart of C
  6. Chapter 5: Structs and Typedef
  7. Chapter 6: Arrays and Memory
  8. Chapter 7: Preprocessor and Macros
  9. Chapter 8: Advanced Macros and Metaprogramming
  10. Chapter 9: Memory Management
  11. Chapter 10: Header Files and Project Organization
  12. Chapter 11: Enums and Unions
  13. Chapter 12: Function Pointers and Callbacks
  14. Chapter 13: Building Complete Programs

Introduction to C and Clay

What is C?

C is a powerful, low-level programming language created in 1972. It's:

  • Fast: Compiles to native machine code
  • Portable: Runs on almost any hardware
  • Simple: Small set of keywords and features
  • Powerful: Direct memory access and hardware control
  • Foundation: Many languages (C++, Java, Python internals) are built in C

What is Clay?

Clay is a high-performance 2D UI layout library that demonstrates professional C programming:

  • Single-header library: Entire implementation in one file
  • Zero dependencies: No standard library required
  • Microsecond performance: Extremely fast
  • Production-ready: Real-world, well-designed code

Why Learn C with Clay?

Clay shows you:

  • Professional C code patterns
  • Real-world memory management
  • Advanced macro techniques
  • API design principles
  • Performance optimization

Let's begin!


Chapter 1: C Basics - Your First Program

1.1 The Simplest C Program

Every C program starts with a main function:

int main(void) {
    return 0;
}

Breaking it down:

  • int - The function returns an integer (0 = success)
  • main - Special function name (program entry point)
  • void - Takes no parameters
  • return 0 - Exit code (0 means success)

1.2 Including Headers

To use functions from other files, we include headers:

#include <stdio.h>  // Standard Input/Output

int main(void) {
    printf("Hello, World!\n");
    return 0;
}

Clay Example:

#define CLAY_IMPLEMENTATION
#include "clay.h"  // Include Clay library

int main(void) {
    // Clay initialization code here
    return 0;
}

1.3 Comments

Two types of comments in C:

// Single-line comment

/*
   Multi-line comment
   Can span multiple lines
*/

Clay Example (from clay.h:1):

/*
    Clay 0.12 - A High Performance UI Layout Library in C

    Features:
    - Flexbox-style responsive layout
    - Single header library
    - Microsecond layout performance
*/

1.4 Key Concepts Learned

  • Basic program structure
  • The main() function
  • Including headers with #include
  • Comments

Chapter 2: Variables and Data Types

2.1 Basic Data Types

C has several built-in types:

int main(void) {
    // Integer types
    int age = 25;              // Signed integer (usually 32 bits)
    unsigned int count = 100;  // Unsigned (only positive)

    // Floating-point types
    float pi = 3.14f;          // Single precision
    double precise = 3.14159;  // Double precision

    // Character type
    char letter = 'A';         // Single character

    // Boolean (C99+)
    _Bool isTrue = 1;          // 0 = false, 1 = true

    return 0;
}

2.2 Fixed-Width Integer Types

Modern C uses fixed-width types for portability:

#include <stdint.h>

int main(void) {
    int8_t   small = 127;      // 8-bit signed (-128 to 127)
    uint8_t  byte = 255;       // 8-bit unsigned (0 to 255)
    int16_t  medium = 32767;   // 16-bit signed
    uint16_t umedium = 65535;  // 16-bit unsigned
    int32_t  large = 2147483647;   // 32-bit signed
    uint32_t ularge = 4294967295;  // 32-bit unsigned
    int64_t  huge = 9223372036854775807; // 64-bit signed

    return 0;
}

Clay Example (from clay.h):

// Clay uses fixed-width types for precision
typedef struct {
    int32_t capacity;   // Exactly 32 bits
    int32_t length;
    uint32_t *internalArray;  // Unsigned 32-bit
} Clay__int32_tArray;

2.3 Type Sizes

#include <stdio.h>

int main(void) {
    printf("int size: %zu bytes\n", sizeof(int));
    printf("float size: %zu bytes\n", sizeof(float));
    printf("double size: %zu bytes\n", sizeof(double));
    printf("char size: %zu bytes\n", sizeof(char));

    return 0;
}

2.4 Constants

Make values unchangeable:

const int MAX_ITEMS = 100;  // Cannot be changed
const float PI = 3.14159f;

Clay Example (from clay.h):

const Clay_Color CLAY_COLOR_WHITE = {255, 255, 255, 255};
const Clay_Color CLAY_COLOR_BLACK = {0, 0, 0, 255};

2.5 Key Concepts Learned

  • Basic types: int, float, double, char
  • Fixed-width types: int32_t, uint32_t, etc.
  • sizeof operator
  • const keyword

Chapter 3: Functions

3.1 Function Basics

Functions organize code into reusable blocks:

// Function declaration (prototype)
int add(int a, int b);

int main(void) {
    int result = add(5, 3);  // Call the function
    return 0;
}

// Function definition
int add(int a, int b) {
    return a + b;
}

Parts of a function:

  • int - Return type
  • add - Function name
  • (int a, int b) - Parameters
  • { ... } - Function body

3.2 Void Functions

Functions that don't return a value:

void printMessage(void) {
    printf("Hello!\n");
}

int main(void) {
    printMessage();  // No return value
    return 0;
}

Clay Example (from clay.h):

void Clay_BeginLayout(void) {
    // Starts a new layout frame
    Clay__currentContext->layoutElementsHashMapInternal.length = 0;
    Clay__treeNodeVisited.length = 0;
    // ... more initialization
}

3.3 Function with Multiple Parameters

float calculateArea(float width, float height) {
    return width * height;
}

int main(void) {
    float area = calculateArea(10.5f, 5.2f);
    return 0;
}

Clay Example (from clay.h):

Clay_Dimensions Clay__MeasureTextCached(
    Clay_String *text,
    Clay_TextElementConfig *config
) {
    // Custom text measurement function
    Clay_Dimensions dimensions = {0};
    // ... measurement logic
    return dimensions;
}

3.4 Static Functions (Internal Linkage)

static makes functions private to a file:

// Only visible in this file
static int helperFunction(int x) {
    return x * 2;
}

int publicFunction(int x) {
    return helperFunction(x);  // Can use it here
}

Clay Example (from clay.h):

// Internal function, not exposed to users
static inline Clay_BoundingBox Clay__HashMapElementBoundingBox(
    int32_t index
) {
    // Implementation details hidden from API users
}

3.5 Inline Functions

inline suggests the compiler to insert code directly (faster):

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

Clay Example (from clay.h):

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;
}

3.6 Key Concepts Learned

  • Function declaration vs definition
  • Parameters and return values
  • void functions
  • static keyword for internal functions
  • inline optimization hint

Chapter 4: Pointers - The Heart of C

4.1 What is a Pointer?

A pointer stores a memory address:

int main(void) {
    int age = 25;        // Regular variable
    int *ptr = &age;     // Pointer to age

    // ptr holds the address of age
    // *ptr accesses the value at that address

    printf("age = %d\n", age);      // 25
    printf("&age = %p\n", &age);    // Address (e.g., 0x7fff5fbff5ac)
    printf("ptr = %p\n", ptr);      // Same address
    printf("*ptr = %d\n", *ptr);    // 25 (dereferencing)

    return 0;
}

Key operators:

  • & - Address-of operator (get address)
  • * - Dereference operator (get value at address)

4.2 Pointer Syntax

int x = 10;
int *p;      // Declare pointer to int
p = &x;      // p now points to x
*p = 20;     // Changes x to 20 through pointer

float y = 3.14f;
float *fp = &y;  // Pointer to float

char c = 'A';
char *cp = &c;   // Pointer to char

4.3 Pointers as Function Parameters

Pass by reference to modify variables:

// Pass by value (doesn't modify original)
void incrementValue(int x) {
    x = x + 1;  // Only modifies local copy
}

// Pass by pointer (modifies original)
void incrementPointer(int *x) {
    *x = *x + 1;  // Modifies original through pointer
}

int main(void) {
    int num = 5;

    incrementValue(num);
    printf("%d\n", num);  // Still 5

    incrementPointer(&num);
    printf("%d\n", num);  // Now 6

    return 0;
}

Clay Example (from clay.h):

// Takes pointer to modify the element
void Clay__OpenElement(void) {
    Clay_LayoutElement *openLayoutElement =
        Clay__LayoutElementPointerArray_Get(
            &Clay__currentContext->layoutElements,
            Clay__currentContext->layoutElements.length++
        );
    // Modifies element through pointer
}

4.4 NULL Pointers

A pointer to nothing:

int *p = NULL;  // Points to nothing (address 0)

if (p == NULL) {
    printf("Pointer is null\n");
}

// Don't dereference NULL pointers!
// *p = 5;  // CRASH!

Clay Example (from clay.h):

typedef struct {
    Clay_ElementId elementId;
    Clay_LayoutElement *layoutElement;  // Can be NULL
    Clay_BoundingBox boundingBox;
} Clay__LayoutElementTreeNode;

// Check before use
if (layoutElement != NULL) {
    // Safe to use
}

4.5 Pointer Arithmetic

Pointers can be incremented/decremented:

int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;  // Points to first element

printf("%d\n", *p);      // 10
p++;                     // Move to next element
printf("%d\n", *p);      // 20
p += 2;                  // Move 2 elements forward
printf("%d\n", *p);      // 40

Clay Example (from clay.h):

// Iterate through array with pointer
char *chars = string.chars;
for (int i = 0; i < string.length; i++) {
    char current = chars[i];  // or *(chars + i)
    // Process character
}

4.6 Key Concepts Learned

  • Pointers store memory addresses
  • & (address-of) and * (dereference) operators
  • Pass by pointer to modify variables
  • NULL pointers
  • Pointer arithmetic

Chapter 5: Structs and Typedef

Structs group multiple variables together:

struct Person {
    char name[50];
    int age;
    float height;
};

int main(void) {
    struct Person john;
    john.age = 30;
    john.height = 5.9f;

    printf("Age: %d\n", john.age);
    return 0;
}

5.2 Typedef - Creating Type Aliases

typedef creates shorter names:

// Without typedef
struct Person {
    char name[50];
    int age;
};
struct Person john;  // Must write "struct"

// With typedef
typedef struct {
    char name[50];
    int age;
} Person;

Person john;  // Cleaner!

Clay Example (from clay.h):

typedef struct {
    float x, y;
} Clay_Vector2;

typedef struct {
    float width, height;
} Clay_Dimensions;

typedef struct {
    float r, g, b, a;
} Clay_Color;

5.3 Nested Structs

Structs can contain other structs:

typedef struct {
    float x, y;
} Point;

typedef struct {
    Point topLeft;
    Point bottomRight;
} Rectangle;

int main(void) {
    Rectangle rect;
    rect.topLeft.x = 0.0f;
    rect.topLeft.y = 0.0f;
    rect.bottomRight.x = 100.0f;
    rect.bottomRight.y = 50.0f;

    return 0;
}

Clay Example (from clay.h):

typedef struct {
    Clay_Vector2 x, y;  // Nested struct
} Clay_BoundingBox;

typedef struct {
    Clay_BoundingBox boundingBox;  // Nested
    Clay_Dimensions dimensions;    // Nested
    Clay_LayoutConfig config;      // Nested
} Clay_LayoutElement;

5.4 Struct Initialization

Multiple ways to initialize structs:

typedef struct {
    int x;
    int y;
    int z;
} Point3D;

// Method 1: Member by member
Point3D p1;
p1.x = 1;
p1.y = 2;
p1.z = 3;

// Method 2: Initializer list (order matters)
Point3D p2 = {1, 2, 3};

// Method 3: Designated initializers (C99+)
Point3D p3 = {.x = 1, .y = 2, .z = 3};
Point3D p4 = {.z = 3, .x = 1, .y = 2};  // Order doesn't matter!

// Partial initialization (rest = 0)
Point3D p5 = {.x = 1};  // y=0, z=0

Clay Example (from clay.h):

// Designated initializers for clean API
Clay_Color white = {.r = 255, .g = 255, .b = 255, .a = 255};

Clay_Dimensions size = {.width = 100, .height = 50};

Clay_BoundingBox box = {
    .x = {.min = 0, .max = 100},
    .y = {.min = 0, .max = 50}
};

5.5 Pointers to Structs

Use -> to access members through pointers:

typedef struct {
    int x, y;
} Point;

int main(void) {
    Point p = {10, 20};
    Point *ptr = &p;

    // Two ways to access through pointer:
    (*ptr).x = 30;   // Dereference then access
    ptr->y = 40;     // Arrow operator (cleaner)

    printf("(%d, %d)\n", p.x, p.y);  // (30, 40)
    return 0;
}

Clay Example (from clay.h):

Clay_LayoutElement *element = GetLayoutElement();

// Access members with ->
element->dimensions.width = 100;
element->dimensions.height = 50;
element->childrenOrTextContent.children.length = 0;

5.6 Forward Declarations

Declare struct name before definition:

// Forward declaration
typedef struct Clay_Context Clay_Context;

// Can now use Clay_Context* in other structs
typedef struct {
    Clay_Context *context;
} SomeStruct;

// Full definition later
struct Clay_Context {
    int32_t maxElementCount;
    // ... other members
};

Clay Example (from clay.h:287):

typedef struct Clay_Context Clay_Context;

// Used before full definition
Clay_Context* Clay_GetCurrentContext(void);

5.7 Key Concepts Learned

  • struct for grouping data
  • typedef for type aliases
  • Nested structs
  • Designated initializers
  • -> operator for pointer member access
  • Forward declarations

Chapter 6: Arrays and Memory

6.1 Static Arrays

Fixed-size arrays declared at compile time:

int main(void) {
    int numbers[5];           // Array of 5 ints
    numbers[0] = 10;          // First element
    numbers[4] = 50;          // Last element

    // Initialize on declaration
    int values[5] = {1, 2, 3, 4, 5};

    // Partial initialization (rest = 0)
    int zeros[10] = {0};  // All zeros

    // Size inferred from initializer
    int items[] = {10, 20, 30};  // Size = 3

    return 0;
}

6.2 Array and Pointer Relationship

Important: Array names decay to pointers!

int main(void) {
    int arr[5] = {10, 20, 30, 40, 50};

    int *p = arr;  // arr decays to pointer to first element

    printf("%d\n", arr[0]);   // 10
    printf("%d\n", *arr);     // 10 (same)
    printf("%d\n", *(arr+1)); // 20
    printf("%d\n", arr[1]);   // 20 (same)

    return 0;
}

6.3 Passing Arrays to Functions

Arrays are always passed as pointers:

// These are equivalent:
void processArray1(int arr[], int size) { }
void processArray2(int *arr, int size) { }

int main(void) {
    int numbers[5] = {1, 2, 3, 4, 5};

    // Must pass size separately!
    processArray1(numbers, 5);
    processArray2(numbers, 5);

    return 0;
}

Clay Example (from clay.h):

// Takes pointer and length
static inline void Clay__MeasureTextCached(
    Clay_String *text,  // Pointer to string (char array)
    Clay_TextElementConfig *config
) {
    // text->chars is char array
    // text->length is size
}

6.4 Multidimensional Arrays

int main(void) {
    // 2D array: 3 rows, 4 columns
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    printf("%d\n", matrix[0][0]);  // 1
    printf("%d\n", matrix[1][2]);  // 7
    printf("%d\n", matrix[2][3]);  // 12

    return 0;
}

6.5 Flexible Array Members

Last member of struct can be flexible:

typedef struct {
    int length;
    int items[];  // Flexible array (must be last)
} DynamicArray;

// Allocate with specific size
DynamicArray *arr = malloc(sizeof(DynamicArray) + 10 * sizeof(int));
arr->length = 10;
arr->items[0] = 100;

Clay Example (from clay.h):

typedef struct {
    int32_t capacity;
    int32_t length;
    Clay_ElementId *internalArray;  // Pointer acts like flexible array
} Clay__ElementIdArray;

6.6 String Arrays

Strings in C are char arrays ending with '\0':

int main(void) {
    // String literal
    char *str1 = "Hello";  // Points to read-only memory

    // Char array
    char str2[] = "Hello";  // Mutable, size = 6 (includes \0)

    // Explicit size
    char str3[10] = "Hello";  // Rest filled with \0

    // Character by character
    char str4[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

    return 0;
}

Clay Example (from clay.h):

// Clay uses explicit length instead of \0
typedef struct {
    int32_t length;
    const char *chars;  // Not null-terminated!
} Clay_String;

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

6.7 sizeof with Arrays

int main(void) {
    int arr[10];

    size_t arrayBytes = sizeof(arr);        // 40 (10 * 4)
    size_t elementBytes = sizeof(arr[0]);   // 4
    size_t arrayLength = sizeof(arr) / sizeof(arr[0]);  // 10

    // Warning: This doesn't work with pointers!
    int *p = arr;
    size_t pointerSize = sizeof(p);  // 8 (on 64-bit), not 40!

    return 0;
}

6.8 Key Concepts Learned

  • Static array declaration
  • Array initialization
  • Arrays decay to pointers
  • Passing arrays to functions
  • Multidimensional arrays
  • C strings (null-terminated char arrays)
  • sizeof with arrays

Chapter 7: Preprocessor and Macros

7.1 What is the Preprocessor?

The preprocessor runs BEFORE compilation and performs text substitution:

#include <stdio.h>   // Insert file contents
#define MAX 100      // Text replacement

int main(void) {
    int arr[MAX];    // Becomes: int arr[100];
    return 0;
}

7.2 #define - Simple Macros

#define PI 3.14159
#define MAX_SIZE 1000
#define PROGRAM_NAME "MyApp"

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

Clay Example (from clay.h:102):

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

7.3 #ifdef and Conditional Compilation

#define DEBUG

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

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

    return 0;
}

Clay Example (from clay.h:82):

#ifndef CLAY_HEADER
#define CLAY_HEADER

// Header contents here...

#endif // CLAY_HEADER

This prevents multiple inclusion of the same header.

7.4 Function-like Macros

Macros can take arguments:

#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int main(void) {
    int result = SQUARE(5);     // (5) * (5) = 25
    int max = MAX(10, 20);      // 20

    // Warning: Be careful!
    int bad = SQUARE(2 + 3);    // (2 + 3) * (2 + 3) = 25 ✓
    // Without parentheses would be: 2 + 3 * 2 + 3 = 11 ✗

    return 0;
}

Clay Example (from clay.h:111):

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

7.5 Multi-line Macros

Use backslash to continue lines:

#define SWAP(a, b) \
    do { \
        typeof(a) temp = a; \
        a = b; \
        b = temp; \
    } while(0)

int main(void) {
    int x = 5, y = 10;
    SWAP(x, y);
    printf("%d %d\n", x, y);  // 10 5
    return 0;
}

Why do { } while(0)? It ensures the macro behaves like a statement:

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

Clay Example (from clay.h):

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

7.6 Stringification (#)

Convert macro argument to string:

#define TO_STRING(x) #x

int main(void) {
    printf("%s\n", TO_STRING(Hello));  // "Hello"
    printf("%s\n", TO_STRING(123));    // "123"
    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 "0.12.0"

7.7 Token Pasting (##)

Concatenate tokens:

#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;
}

Clay Example (from clay.h):

#define CLAY__ARRAY_DEFINE(typeName, arrayName) \
    typedef struct { \
        int32_t capacity; \
        int32_t length; \
        typeName *internalArray; \
    } arrayName;

// Creates type: Clay__int32_tArray
CLAY__ARRAY_DEFINE(int32_t, Clay__int32_tArray)

7.8 Predefined Macros

C provides built-in 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 name (C99)

    return 0;
}

7.9 Header Guards

Prevent multiple inclusion:

// myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H

// Header contents here

#endif // MYHEADER_H

Clay Example (from clay.h:82):

#ifndef CLAY_HEADER
#define CLAY_HEADER

// All Clay declarations

#endif // CLAY_HEADER

7.10 Key Concepts Learned

  • Preprocessor runs before compilation
  • #define for constants and macros
  • #ifdef, #ifndef for conditional compilation
  • Function-like macros with parameters
  • Multi-line macros with backslash
  • # for stringification
  • ## for token pasting
  • Header guards

Chapter 8: Advanced Macros and Metaprogramming

8.1 Variadic Macros

Macros that accept variable number of arguments:

#define LOG(format, ...) \
    printf("[LOG] " format "\n", __VA_ARGS__)

int main(void) {
    LOG("Value: %d", 42);
    LOG("X: %d, Y: %d", 10, 20);
    return 0;
}

__VA_ARGS__ represents all extra arguments.

Clay Example (from clay.h):

#define CLAY__INIT(type) \
    (type)

// Usage with designated initializers
Clay_Color color = CLAY__INIT(Clay_Color) {
    .r = 255, .g = 0, .b = 0, .a = 255
};

8.2 X-Macros Pattern

Generate code from data:

// Define data once
#define COLOR_LIST \
    X(RED, 0xFF0000) \
    X(GREEN, 0x00FF00) \
    X(BLUE, 0x0000FF)

// Generate enum
enum Colors {
    #define X(name, value) COLOR_##name,
    COLOR_LIST
    #undef X
};

// Generate array
const char* colorNames[] = {
    #define X(name, value) #name,
    COLOR_LIST
    #undef X
};

int main(void) {
    printf("%s\n", colorNames[COLOR_RED]);  // "RED"
    return 0;
}

8.3 Generic Macros with _Generic (C11)

Type-based selection at compile time:

#define max(a, b) _Generic((a), \
    int: max_int, \
    float: max_float, \
    double: max_double \
)(a, b)

int max_int(int a, int b) { return a > b ? a : b; }
float max_float(float a, float b) { return a > b ? a : b; }
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(5.5f, 10.2f);  // Calls max_float
    return 0;
}

8.4 Compound Literals

Create temporary struct values:

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){.x = 10, .y = 20});

    // Another example
    Point *p = &(Point){.x = 5, .y = 15};

    return 0;
}

Clay Example (from clay.h):

// Macro uses compound literals for clean API
#define CLAY_COLOR(r, g, b, a) \
    (Clay_Color) {.r = r, .g = b, .b = b, .a = a}

// Usage
Clay_Color red = CLAY_COLOR(255, 0, 0, 255);

8.5 Statement Expressions (GCC Extension)

Create macros that return values:

#define MAX(a, b) ({ \
    typeof(a) _a = (a); \
    typeof(b) _b = (b); \
    _a > _b ? _a : _b; \
})

int main(void) {
    int x = MAX(5 + 2, 3 + 4);  // Evaluates each expression once
    return 0;
}

8.6 The For-Loop Macro Trick

Create scope-based macros:

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

Clay Example (from clay.h:2016) - This is ADVANCED:

#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 creates automatic open/close pairs
CLAY({ .layout = { .padding = CLAY_PADDING_ALL(8) } }) {
    // Child elements
}
// Automatically closes element

How it works:

  1. Init: Clay__OpenElement(), configure, set latch to 0
  2. Condition: latch < 1 is true (0 < 1), enter loop
  3. Body: User code executes
  4. Increment: Set latch to 1, Clay__CloseElement()
  5. Condition: latch < 1 is false (1 < 1), exit loop

This ensures CloseElement is always called!

8.7 Designated Initializers in Macros

#define CREATE_RECT(w, h) \
    (Rectangle) { \
        .width = (w), \
        .height = (h), \
        .x = 0, \
        .y = 0 \
    }

int main(void) {
    Rectangle r = CREATE_RECT(100, 50);
    return 0;
}

Clay Example (from clay.h):

#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) }
);

8.8 Macro Debugging Tips

// Use #pragma to see macro expansion
#define COMPLEX_MACRO(x) ((x) * 2 + 1)

int main(void) {
    #pragma message "COMPLEX_MACRO(5) expands to:"
    int result = COMPLEX_MACRO(5);
    return 0;
}

// Compile with gcc -E to see preprocessor output

8.9 Key Concepts Learned

  • Variadic macros (VA_ARGS)
  • X-Macros for code generation
  • _Generic for type-based selection
  • Compound literals
  • Statement expressions
  • For-loop macro trick
  • Designated initializers in macros

Chapter 9: Memory Management

9.1 Stack vs Heap

Stack Memory:

  • Automatic allocation/deallocation
  • Fast
  • Limited size
  • Variables disappear when function returns
void function(void) {
    int x = 10;        // On stack
    char str[100];     // On stack
}  // x and str are destroyed here

Heap Memory:

  • Manual allocation (malloc) and deallocation (free)
  • Slower
  • Large size available
  • Persists until freed
#include <stdlib.h>

void function(void) {
    int *p = malloc(sizeof(int));  // On heap
    *p = 10;
    free(p);  // Must manually free!
}

9.2 malloc, calloc, realloc, free

#include <stdlib.h>

int main(void) {
    // malloc - allocate uninitialized memory
    int *arr1 = malloc(10 * sizeof(int));
    if (arr1 == NULL) {
        // Allocation failed!
        return 1;
    }

    // calloc - allocate zero-initialized memory
    int *arr2 = calloc(10, sizeof(int));  // All zeros

    // realloc - resize allocated memory
    arr1 = realloc(arr1, 20 * sizeof(int));

    // free - deallocate memory
    free(arr1);
    free(arr2);

    return 0;
}

9.3 Common Memory Errors

// 1. Memory leak - allocated but never freed
void leak(void) {
    int *p = malloc(sizeof(int));
    // Forgot to free(p)!
}

// 2. Use after free
void useAfterFree(void) {
    int *p = malloc(sizeof(int));
    free(p);
    *p = 10;  // DANGEROUS! Undefined behavior
}

// 3. Double free
void doubleFree(void) {
    int *p = malloc(sizeof(int));
    free(p);
    free(p);  // CRASH!
}

// 4. Accessing uninitialized memory
void uninit(void) {
    int *p = malloc(sizeof(int));
    printf("%d\n", *p);  // Random value!
    free(p);
}

9.4 Arena Allocators

Instead of many malloc/free calls, allocate one large block:

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) {
    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
}

int main(void) {
    Arena arena;
    Arena_Init(&arena, 1024 * 1024);  // 1MB

    int *arr1 = Arena_Alloc(&arena, 100 * sizeof(int));
    float *arr2 = Arena_Alloc(&arena, 50 * sizeof(float));

    // Use allocations...

    Arena_Free(&arena);  // Free everything
    return 0;
}

Clay Example (from clay.h:185):

typedef struct {
    uintptr_t nextAllocation;
    size_t capacity;
    char *memory;
} Clay_Arena;

// Clay allocates everything from arenas - no malloc in hot path!
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
}

9.5 Memory Alignment

CPUs prefer aligned memory access:

#include <stdint.h>

// Proper alignment
struct Aligned {
    uint64_t a;  // 8 bytes, aligned to 8
    uint32_t b;  // 4 bytes
    uint32_t c;  // 4 bytes
};  // Total: 16 bytes

// Poor alignment - compiler adds padding
struct Unaligned {
    uint8_t a;   // 1 byte
    // 7 bytes padding
    uint64_t b;  // 8 bytes
    uint8_t c;   // 1 byte
    // 7 bytes padding
};  // Total: 24 bytes instead of 10!

Clay Example (from clay.h):

// Careful struct layout for performance
typedef struct {
    float width, height;  // 8 bytes total
} Clay_Dimensions;  // Aligned to 4 bytes

typedef struct {
    Clay_Vector2 x, y;    // 16 bytes total
} Clay_BoundingBox;  // Aligned to 4 bytes

9.6 Memory Pools

Pre-allocate objects of same size:

#define POOL_SIZE 100

typedef struct Node {
    int value;
    struct Node *next;
} Node;

typedef struct {
    Node nodes[POOL_SIZE];
    Node *freeList;
} 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];
}

Node* Pool_Alloc(NodePool *pool) {
    if (pool->freeList == NULL) return NULL;

    Node *node = pool->freeList;
    pool->freeList = node->next;
    return node;
}

void Pool_Free(NodePool *pool, Node *node) {
    node->next = pool->freeList;
    pool->freeList = node;
}

9.7 Key Concepts Learned

  • Stack vs heap memory
  • malloc, calloc, realloc, free
  • Common memory errors
  • Arena allocators
  • Memory alignment
  • Memory pools

Chapter 10: Header Files and Project Organization

10.1 Header Files Basics

Header file (.h): Declarations (interface) Source file (.c): Definitions (implementation)

// math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H

int add(int a, int b);        // Declaration only
int multiply(int a, int b);

#endif
// math_utils.c
#include "math_utils.h"

int add(int a, int b) {       // Definition
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}
// main.c
#include "math_utils.h"

int main(void) {
    int result = add(5, 3);
    return 0;
}

10.2 Header Guards

Prevent multiple inclusion:

// myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H

// Declarations

#endif // MYHEADER_H

Alternative (non-standard but widely supported):

#pragma once

// Declarations

10.3 Include Order

Best practice:

// In myfile.c:

// 1. Corresponding header
#include "myfile.h"

// 2. System headers
#include <stdio.h>
#include <stdlib.h>

// 3. Third-party headers
#include "external_lib.h"

// 4. Project headers
#include "project_utils.h"

10.4 Single-Header Library Pattern

Entire library in one header file:

// mylib.h
#ifndef MYLIB_H
#define MYLIB_H

// Declarations (always included)
void myFunction(void);

// Implementation (included only once)
#ifdef MYLIB_IMPLEMENTATION

void myFunction(void) {
    // Implementation here
}

#endif // MYLIB_IMPLEMENTATION
#endif // MYLIB_H

Usage:

// In ONE .c file:
#define MYLIB_IMPLEMENTATION
#include "mylib.h"

// In other files:
#include "mylib.h"

Clay Example (clay.h structure):

#ifndef CLAY_HEADER
#define CLAY_HEADER

// ===== PUBLIC API DECLARATIONS =====
typedef struct { /* ... */ } Clay_Dimensions;
void Clay_BeginLayout(void);
// ... more declarations

// ===== IMPLEMENTATION =====
#ifdef CLAY_IMPLEMENTATION

// All implementation code here
void Clay_BeginLayout(void) {
    // ...
}

#endif // CLAY_IMPLEMENTATION
#endif // CLAY_HEADER

10.5 Forward Declarations

Declare types before full definition:

// Forward declaration
typedef struct Node Node;

typedef struct {
    Node *next;  // Can use pointer to Node
} List;

// Full definition later
struct Node {
    int value;
    Node *next;
};

Clay Example (from clay.h:287):

typedef struct Clay_Context Clay_Context;

// Can now use Clay_Context* in functions
Clay_Context* Clay_GetCurrentContext(void);

// Full definition comes later (line 1900+)
struct Clay_Context {
    // ...
};

10.6 Opaque Pointers

Hide implementation details:

// widget.h
typedef struct Widget Widget;  // Opaque type

Widget* Widget_Create(void);
void Widget_Destroy(Widget *w);
void Widget_SetValue(Widget *w, int value);
// widget.c
struct Widget {  // Full definition only in .c file
    int value;
    int internal_state;
};

Widget* Widget_Create(void) {
    Widget *w = malloc(sizeof(Widget));
    w->value = 0;
    w->internal_state = 0;
    return w;
}

Users can't access internal members directly!

10.7 Conditional Compilation for Platforms

#ifdef _WIN32
    #include <windows.h>
    #define SLEEP(ms) Sleep(ms)
#else
    #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:88):

#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

10.8 Key Concepts Learned

  • Header vs source files
  • Header guards
  • Include order best practices
  • Single-header library pattern
  • Forward declarations
  • Opaque pointers
  • Platform-specific code

Chapter 11: Enums and Unions

11.1 Enums - Named Constants

enum Color {
    COLOR_RED,      // 0
    COLOR_GREEN,    // 1
    COLOR_BLUE      // 2
};

int main(void) {
    enum Color myColor = COLOR_RED;

    if (myColor == COLOR_RED) {
        printf("Red!\n");
    }

    return 0;
}

11.2 Enums with Typedef

typedef enum {
    COLOR_RED,
    COLOR_GREEN,
    COLOR_BLUE
} Color;

Color myColor = COLOR_RED;  // Cleaner syntax

11.3 Custom Enum Values

typedef enum {
    ERROR_NONE = 0,
    ERROR_FILE_NOT_FOUND = 1,
    ERROR_PERMISSION_DENIED = 2,
    ERROR_OUT_OF_MEMORY = 100,
    ERROR_UNKNOWN = -1
} ErrorCode;

Clay Example (from clay.h:208):

typedef enum {
    CLAY_SIZING_TYPE_FIT,
    CLAY_SIZING_TYPE_GROW,
    CLAY_SIZING_TYPE_PERCENT,
    CLAY_SIZING_TYPE_FIXED
} Clay_SizingType;

typedef enum {
    CLAY_LAYOUT_DIRECTION_LEFT_TO_RIGHT,
    CLAY_LAYOUT_DIRECTION_TOP_TO_BOTTOM
} Clay_LayoutDirection;

11.4 Enum Flags (Bit Flags)

typedef enum {
    FLAG_NONE     = 0,      // 0000
    FLAG_READ     = 1 << 0, // 0001
    FLAG_WRITE    = 1 << 1, // 0010
    FLAG_EXECUTE  = 1 << 2, // 0100
    FLAG_ADMIN    = 1 << 3  // 1000
} Permissions;

int main(void) {
    Permissions perms = FLAG_READ | FLAG_WRITE;  // Combine flags

    if (perms & FLAG_READ) {
        printf("Can read\n");
    }

    perms |= FLAG_EXECUTE;   // Add execute
    perms &= ~FLAG_WRITE;    // Remove write

    return 0;
}

Clay Example (from clay.h:252):

typedef enum {
    CLAY_CORNER_RADIUS_NONE = 0,
    CLAY_CORNER_RADIUS_TOP_LEFT = 1 << 0,
    CLAY_CORNER_RADIUS_TOP_RIGHT = 1 << 1,
    CLAY_CORNER_RADIUS_BOTTOM_LEFT = 1 << 2,
    CLAY_CORNER_RADIUS_BOTTOM_RIGHT = 1 << 3,
    CLAY_CORNER_RADIUS_ALL =
        CLAY_CORNER_RADIUS_TOP_LEFT |
        CLAY_CORNER_RADIUS_TOP_RIGHT |
        CLAY_CORNER_RADIUS_BOTTOM_LEFT |
        CLAY_CORNER_RADIUS_BOTTOM_RIGHT
} Clay_CornerRadiusSet;

11.5 Unions - Same Memory, Different Types

union Data {
    int i;
    float f;
    char str[20];
};

int main(void) {
    union Data data;

    data.i = 10;
    printf("%d\n", data.i);    // 10

    data.f = 3.14f;            // Overwrites i
    printf("%f\n", data.f);    // 3.14
    // data.i is now garbage!

    printf("Size: %zu\n", sizeof(data));  // 20 (largest member)

    return 0;
}

11.6 Tagged Unions

Track which union member is active:

typedef enum {
    TYPE_INT,
    TYPE_FLOAT,
    TYPE_STRING
} ValueType;

typedef struct {
    ValueType type;  // Tag
    union {
        int i;
        float f;
        char *s;
    } data;
} Value;

int main(void) {
    Value v;

    v.type = TYPE_INT;
    v.data.i = 42;

    // Safe access based on tag
    switch (v.type) {
        case TYPE_INT:
            printf("Int: %d\n", v.data.i);
            break;
        case TYPE_FLOAT:
            printf("Float: %f\n", v.data.f);
            break;
        case TYPE_STRING:
            printf("String: %s\n", v.data.s);
            break;
    }

    return 0;
}

Clay Example (from clay.h:213):

typedef struct {
    Clay_SizingType type;  // Tag
    union {
        float sizeMinMax;
        struct {
            float min;
            float max;
        } minMax;
        float sizePercent;
    } size;
} Clay_Sizing;

// Usage
Clay_Sizing sizing;
sizing.type = CLAY_SIZING_TYPE_FIXED;
sizing.size.sizeMinMax = 100.0f;  // Safe because type is FIXED

11.7 Anonymous Unions and Structs (C11)

typedef struct {
    enum { TYPE_INT, TYPE_FLOAT } type;
    union {  // Anonymous union
        int i;
        float f;
    };  // No name!
} Value;

int main(void) {
    Value v;
    v.type = TYPE_INT;
    v.i = 42;  // Access directly, not v.data.i

    return 0;
}

11.8 Key Concepts Learned

  • Enums for named constants
  • Typedef with enums
  • Bit flags with enums
  • Unions for memory-efficient storage
  • Tagged unions for type safety
  • Anonymous unions

Chapter 12: Function Pointers and Callbacks

12.1 Function Pointer Basics

Functions have addresses too!

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}

int main(void) {
    // Function pointer syntax: return_type (*name)(params)
    int (*operation)(int, int);

    operation = add;
    printf("%d\n", operation(5, 3));  // 8

    operation = multiply;
    printf("%d\n", operation(5, 3));  // 15

    return 0;
}

12.2 Typedef with Function Pointers

Make syntax cleaner:

// Without typedef
int (*callback)(int, int);

// With typedef
typedef int (*BinaryOperation)(int, int);

BinaryOperation callback;  // Much cleaner!

Clay Example (from clay.h:166):

typedef void (*Clay_ErrorHandler)(Clay_ErrorData errorData);

typedef struct {
    Clay_ErrorHandler errorHandlerFunction;
    void *userData;
} Clay_ErrorHandlerFunction;

12.3 Callbacks

Pass functions as parameters:

#include <stdio.h>

typedef void (*Callback)(int);

void processArray(int *arr, int size, Callback func) {
    for (int i = 0; i < size; i++) {
        func(arr[i]);  // Call the callback
    }
}

void printDouble(int x) {
    printf("%d ", x * 2);
}

void printSquare(int x) {
    printf("%d ", x * x);
}

int main(void) {
    int arr[] = {1, 2, 3, 4, 5};

    processArray(arr, 5, printDouble);  // 2 4 6 8 10
    printf("\n");
    processArray(arr, 5, printSquare);  // 1 4 9 16 25
    printf("\n");

    return 0;
}

Clay Example (from clay.h:1037):

typedef Clay_Dimensions (*Clay_MeasureTextFunction)(
    Clay_String *text,
    Clay_TextElementConfig *config,
    void *userData
);

// Store in config
typedef struct {
    Clay_MeasureTextFunction measureTextFunction;
    void *userData;
} Clay_TextMeasureFunction;

// Usage
Clay_Dimensions measureText(
    Clay_String *text,
    Clay_TextElementConfig *config,
    void *userData
) {
    // Custom measurement
    return (Clay_Dimensions){100, 20};
}

// Set callback
Clay_SetMeasureTextFunction(measureText, NULL);

12.4 Function Pointer Arrays

#include <stdio.h>

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return a / b; }

int main(void) {
    // Array of function pointers
    int (*operations[4])(int, int) = {add, sub, mul, div};

    printf("%d\n", operations[0](10, 5));  // 15 (add)
    printf("%d\n", operations[1](10, 5));  // 5 (sub)
    printf("%d\n", operations[2](10, 5));  // 50 (mul)
    printf("%d\n", operations[3](10, 5));  // 2 (div)

    return 0;
}

12.5 Callbacks with User Data

typedef void (*Callback)(int value, void *userData);

void forEach(int *arr, int size, Callback func, void *userData) {
    for (int i = 0; i < size; i++) {
        func(arr[i], userData);
    }
}

void printWithPrefix(int value, void *userData) {
    char *prefix = (char*)userData;
    printf("%s%d\n", prefix, value);
}

int main(void) {
    int arr[] = {1, 2, 3};
    forEach(arr, 3, printWithPrefix, "Number: ");
    // Output:
    // Number: 1
    // Number: 2
    // Number: 3

    return 0;
}

Clay Example (from clay.h):

typedef void (*Clay_QueryScrollCallback)(
    Clay_ScrollContainerData *scrollData,
    void *userData
);

// Usage with user data
void handleScroll(Clay_ScrollContainerData *data, void *userData) {
    MyContext *ctx = (MyContext*)userData;
    // Use ctx...
}

Clay_SetQueryScrollCallback(handleScroll, &myContext);

12.6 Key Concepts Learned

  • Function pointers store function addresses
  • Typedef for cleaner function pointer syntax
  • Callbacks for customizable behavior
  • Function pointer arrays
  • User data pattern for context

Chapter 13: Building Complete Programs

13.1 Simple Clay Example

Let's build a complete UI program:

// example.c
#define CLAY_IMPLEMENTATION
#include "clay.h"
#include <stdio.h>
#include <stdlib.h>

// Memory for Clay
Clay_Arena arena;
char arenaMemory[1024 * 1024];  // 1MB

// Text measurement (simplified)
Clay_Dimensions MeasureText(
    Clay_String *text,
    Clay_TextElementConfig *config,
    void *userData
) {
    // Simple: 10 pixels per character, 20 pixels high
    return (Clay_Dimensions){
        .width = text->length * 10,
        .height = 20
    };
}

int main(void) {
    // Initialize Clay
    arena.memory = arenaMemory;
    arena.capacity = sizeof(arenaMemory);

    Clay_Initialize(arena, (Clay_Dimensions){1024, 768});
    Clay_SetMeasureTextFunction(MeasureText, NULL);

    // Begin layout
    Clay_BeginLayout();

    // Create UI hierarchy
    CLAY({
        .layout = {
            .sizing = {
                .width = CLAY_SIZING_GROW(0),
                .height = CLAY_SIZING_GROW(0)
            },
            .padding = CLAY_PADDING_ALL(16),
            .childGap = 16,
            .layoutDirection = CLAY_TOP_TO_BOTTOM
        },
        .backgroundColor = CLAY_COLOR(200, 200, 200, 255)
    }) {
        // Header
        CLAY_TEXT(
            CLAY_STRING("My Application"),
            CLAY_TEXT_CONFIG({
                .fontSize = 24,
                .textColor = CLAY_COLOR(0, 0, 0, 255)
            })
        );

        // Content
        CLAY({
            .layout = {
                .sizing = {
                    .width = CLAY_SIZING_GROW(0),
                    .height = CLAY_SIZING_FIXED(200)
                },
                .padding = CLAY_PADDING_ALL(8)
            },
            .backgroundColor = CLAY_COLOR(255, 255, 255, 255)
        }) {
            CLAY_TEXT(
                CLAY_STRING("This is my UI content!"),
                CLAY_TEXT_CONFIG({
                    .fontSize = 16,
                    .textColor = CLAY_COLOR(0, 0, 0, 255)
                })
            );
        }
    }

    // End layout and get render commands
    Clay_RenderCommandArray commands = Clay_EndLayout();

    // Render (simplified - just print)
    printf("Generated %d render commands\n", commands.length);
    for (int i = 0; i < commands.length; i++) {
        Clay_RenderCommand *cmd = &commands.internalArray[i];
        printf("Command %d: type=%d, bounds=(%.0f,%.0f,%.0f,%.0f)\n",
            i, cmd->commandType,
            cmd->boundingBox.x, cmd->boundingBox.y,
            cmd->boundingBox.width, cmd->boundingBox.height
        );
    }

    return 0;
}

13.2 Compiling

# GCC
gcc -o example example.c -lm

# Clang
clang -o example example.c -lm

# With warnings
gcc -Wall -Wextra -o example example.c -lm

# With optimization
gcc -O2 -o example example.c -lm

13.3 Makefiles

Automate compilation:

# Makefile
CC = gcc
CFLAGS = -Wall -Wextra -O2
LIBS = -lm

example: example.c clay.h
	$(CC) $(CFLAGS) -o example example.c $(LIBS)

clean:
	rm -f example

run: example
	./example

Usage:

make           # Build
make run       # Build and run
make clean     # Remove executable

13.4 Multi-File Project

project/
├── Makefile
├── clay.h
├── src/
│   ├── main.c
│   ├── ui.c
│   ├── ui.h
│   └── utils.c
│   └── utils.h

ui.h:

#ifndef UI_H
#define UI_H

#include "clay.h"

void UI_CreateLayout(void);

#endif

ui.c:

#include "ui.h"

void UI_CreateLayout(void) {
    CLAY({
        .layout = {
            .sizing = {
                .width = CLAY_SIZING_GROW(0),
                .height = CLAY_SIZING_GROW(0)
            }
        }
    }) {
        // UI code
    }
}

main.c:

#define CLAY_IMPLEMENTATION
#include "clay.h"
#include "ui.h"

int main(void) {
    // Initialize Clay

    Clay_BeginLayout();
    UI_CreateLayout();
    Clay_RenderCommandArray commands = Clay_EndLayout();

    // Render...

    return 0;
}

13.5 Common C Patterns Recap

1. Initialization Pattern:

typedef struct {
    int *data;
    size_t size;
} Array;

Array* Array_Create(size_t size) {
    Array *arr = malloc(sizeof(Array));
    arr->data = malloc(size * sizeof(int));
    arr->size = size;
    return arr;
}

void Array_Destroy(Array *arr) {
    free(arr->data);
    free(arr);
}

2. Error Handling:

typedef enum {
    STATUS_OK,
    STATUS_ERROR_NULL_POINTER,
    STATUS_ERROR_OUT_OF_MEMORY
} Status;

Status doSomething(void *data) {
    if (data == NULL) {
        return STATUS_ERROR_NULL_POINTER;
    }

    void *mem = malloc(100);
    if (mem == NULL) {
        return STATUS_ERROR_OUT_OF_MEMORY;
    }

    // Do work...

    free(mem);
    return STATUS_OK;
}

3. Iterator Pattern:

typedef struct {
    int *current;
    int *end;
} Iterator;

Iterator Array_Begin(Array *arr) {
    return (Iterator){arr->data, arr->data + arr->size};
}

bool Iterator_HasNext(Iterator *it) {
    return it->current < it->end;
}

int Iterator_Next(Iterator *it) {
    return *(it->current++);
}

// Usage
Iterator it = Array_Begin(&myArray);
while (Iterator_HasNext(&it)) {
    int value = Iterator_Next(&it);
    printf("%d\n", value);
}

13.6 Key Concepts Learned

  • Complete program structure
  • Compiling with gcc/clang
  • Makefiles for automation
  • Multi-file project organization
  • Common C patterns

Conclusion and Next Steps

What You've Learned

You now understand:

  1. C Fundamentals: Variables, types, functions, control flow
  2. Memory Management: Pointers, stack vs heap, arena allocators
  3. Advanced Types: Structs, unions, enums, typedefs
  4. Preprocessor: Macros, conditional compilation, code generation
  5. Project Organization: Headers, source files, compilation
  6. Real-World Patterns: As demonstrated by Clay library

Clay Demonstrates

  • Professional C code: Clean, well-documented, performant
  • Single-header pattern: Easy distribution and integration
  • Zero dependencies: Portable, minimal C programming
  • Memory efficiency: Arena allocators, no malloc in hot path
  • API design: Clear, consistent, user-friendly
  • Macro DSL: Declarative UI in C

Continue Learning

Read Clay Source:

  • clay.h - Study the implementation
  • examples/ - See real usage
  • renderers/ - Integration with graphics libraries

Build Projects:

  • Simple calculator UI
  • File browser interface
  • Game menu system
  • Settings panel

Explore More:

  • SDL2/Raylib for graphics
  • stb_image for image loading
  • Other single-header libraries

Advanced Topics:

  • SIMD optimization
  • Platform-specific code
  • Custom memory allocators
  • Performance profiling

Resources


Practice Exercises

Exercise 1: Modify Clay Layout

Create a 3-column layout with different colors.

Exercise 2: Custom Text Measurement

Implement proper text measurement using a font library.

Exercise 3: Handle Input

Add mouse click detection to Clay elements.

Exercise 4: Scrolling Container

Create a scrollable list of items.

Exercise 5: Build a Calculator

Create a calculator UI using Clay.


Happy Coding! 🚀

You now have a solid foundation in C programming. Practice regularly, read professional C code like Clay, and build projects to reinforce your learning.