- 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
51 KiB
Learning C Programming with Clay: A Complete Step-by-Step Guide
Table of Contents
- Introduction to C and Clay
- Chapter 1: C Basics - Your First Program
- Chapter 2: Variables and Data Types
- Chapter 3: Functions
- Chapter 4: Pointers - The Heart of C
- Chapter 5: Structs and Typedef
- Chapter 6: Arrays and Memory
- Chapter 7: Preprocessor and Macros
- Chapter 8: Advanced Macros and Metaprogramming
- Chapter 9: Memory Management
- Chapter 10: Header Files and Project Organization
- Chapter 11: Enums and Unions
- Chapter 12: Function Pointers and Callbacks
- 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 parametersreturn 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 typeadd- 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
5.1 Structs - Grouping Related Data
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:
- Init:
Clay__OpenElement(), configure, set latch to 0 - Condition:
latch < 1is true (0 < 1), enter loop - Body: User code executes
- Increment: Set latch to 1,
Clay__CloseElement() - Condition:
latch < 1is 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:
- C Fundamentals: Variables, types, functions, control flow
- Memory Management: Pointers, stack vs heap, arena allocators
- Advanced Types: Structs, unions, enums, typedefs
- Preprocessor: Macros, conditional compilation, code generation
- Project Organization: Headers, source files, compilation
- 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 implementationexamples/- See real usagerenderers/- 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
- Clay Website: https://nicbarker.com/clay
- C Reference: https://en.cppreference.com/w/c
- The C Programming Language by Kernighan & Ritchie
- Modern C by Jens Gustedt
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.