c

examples

examples.cšŸ”§
/**
 * =============================================================================
 * Memory Leaks and Debugging in C - Examples
 * =============================================================================
 * 
 * This file demonstrates memory debugging techniques and common memory issues.
 * 
 * Compilation: gcc -g -O0 -o examples examples.c -Wall -Wextra
 * 
 * For Valgrind:
 *   gcc -g -O0 -o examples examples.c
 *   valgrind --leak-check=full ./examples
 * 
 * For AddressSanitizer:
 *   gcc -fsanitize=address -g -o examples examples.c
 *   ./examples
 * 
 * Topics covered:
 * - Common memory leak patterns
 * - Detection techniques
 * - Debugging wrappers
 * - Memory management patterns
 * =============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

/* ==========================================================================
 * Debug Memory Tracking System
 * ========================================================================== */

/* Enable/disable debug tracking */
#define ENABLE_MEMORY_TRACKING 1

#if ENABLE_MEMORY_TRACKING

/* Allocation record */
typedef struct AllocationRecord {
    void *ptr;
    size_t size;
    const char *file;
    int line;
    const char *func;
    struct AllocationRecord *next;
} AllocationRecord;

/* Global tracking state */
static AllocationRecord *g_allocation_list = NULL;
static size_t g_total_allocated = 0;
static size_t g_total_freed = 0;
static int g_allocation_count = 0;
static int g_free_count = 0;
static int g_tracking_enabled = 1;

/* Track an allocation */
void track_allocation(void *ptr, size_t size, const char *file, 
                      int line, const char *func) {
    if (!g_tracking_enabled || ptr == NULL) return;
    
    AllocationRecord *record = (AllocationRecord *)malloc(sizeof(AllocationRecord));
    if (record) {
        record->ptr = ptr;
        record->size = size;
        record->file = file;
        record->line = line;
        record->func = func;
        record->next = g_allocation_list;
        g_allocation_list = record;
        g_total_allocated += size;
        g_allocation_count++;
    }
}

/* Untrack an allocation */
int untrack_allocation(void *ptr, const char *file, int line, const char *func) {
    if (!g_tracking_enabled || ptr == NULL) return 1;
    
    AllocationRecord **current = &g_allocation_list;
    while (*current) {
        if ((*current)->ptr == ptr) {
            AllocationRecord *to_remove = *current;
            *current = to_remove->next;
            g_total_freed += to_remove->size;
            g_free_count++;
            free(to_remove);
            return 1;
        }
        current = &(*current)->next;
    }
    
    fprintf(stderr, "[MEMORY ERROR] Freeing untracked pointer %p at %s:%d (%s)\n",
            ptr, file, line, func);
    return 0;
}

/* Print memory report */
void print_memory_report(void) {
    printf("\n");
    printf("╔══════════════════════════════════════════════════════════════════╗\n");
    printf("ā•‘                     Memory Report                                ā•‘\n");
    printf("╠══════════════════════════════════════════════════════════════════╣\n");
    printf("ā•‘ Total allocations:      %-10d                              ā•‘\n", g_allocation_count);
    printf("ā•‘ Total frees:            %-10d                              ā•‘\n", g_free_count);
    printf("ā•‘ Total allocated:        %-10zu bytes                        ā•‘\n", g_total_allocated);
    printf("ā•‘ Total freed:            %-10zu bytes                        ā•‘\n", g_total_freed);
    printf("ā•‘ Currently allocated:    %-10zu bytes                        ā•‘\n", 
           g_total_allocated - g_total_freed);
    printf("ā•‘ Outstanding allocations: %-9d                              ā•‘\n", 
           g_allocation_count - g_free_count);
    printf("ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n");
    
    if (g_allocation_list) {
        printf("\nāš ļø  LEAKED ALLOCATIONS:\n");
        printf("─────────────────────────────────────────────────────────────────\n");
        int leak_num = 1;
        size_t total_leaked = 0;
        for (AllocationRecord *r = g_allocation_list; r; r = r->next) {
            printf("  Leak #%d: %zu bytes at %p\n", leak_num++, r->size, r->ptr);
            printf("           Allocated in %s() at %s:%d\n", r->func, r->file, r->line);
            total_leaked += r->size;
        }
        printf("─────────────────────────────────────────────────────────────────\n");
        printf("  TOTAL LEAKED: %zu bytes\n\n", total_leaked);
    } else {
        printf("\nāœ… No memory leaks detected!\n\n");
    }
}

/* Tracked versions of allocation functions */
void *tracked_malloc(size_t size, const char *file, int line, const char *func) {
    void *ptr = malloc(size);
    track_allocation(ptr, size, file, line, func);
    return ptr;
}

void *tracked_calloc(size_t count, size_t size, const char *file, 
                     int line, const char *func) {
    void *ptr = calloc(count, size);
    track_allocation(ptr, count * size, file, line, func);
    return ptr;
}

void *tracked_realloc(void *old_ptr, size_t new_size, const char *file, 
                      int line, const char *func) {
    if (old_ptr) {
        untrack_allocation(old_ptr, file, line, func);
    }
    void *ptr = realloc(old_ptr, new_size);
    if (ptr) {
        track_allocation(ptr, new_size, file, line, func);
    }
    return ptr;
}

void tracked_free(void *ptr, const char *file, int line, const char *func) {
    untrack_allocation(ptr, file, line, func);
    free(ptr);
}

/* Macros to replace standard functions */
#define TRACK_MALLOC(size) tracked_malloc(size, __FILE__, __LINE__, __func__)
#define TRACK_CALLOC(count, size) tracked_calloc(count, size, __FILE__, __LINE__, __func__)
#define TRACK_REALLOC(ptr, size) tracked_realloc(ptr, size, __FILE__, __LINE__, __func__)
#define TRACK_FREE(ptr) tracked_free(ptr, __FILE__, __LINE__, __func__)

#else /* ENABLE_MEMORY_TRACKING disabled */

#define TRACK_MALLOC(size) malloc(size)
#define TRACK_CALLOC(count, size) calloc(count, size)
#define TRACK_REALLOC(ptr, size) realloc(ptr, size)
#define TRACK_FREE(ptr) free(ptr)
#define print_memory_report() ((void)0)

#endif /* ENABLE_MEMORY_TRACKING */

/* ==========================================================================
 * Function Prototypes
 * ========================================================================== */

void print_separator(const char *title);
void example_01_simple_leak(void);
void example_02_leak_on_error_path(void);
void example_03_leak_proper_cleanup(void);
void example_04_reassign_without_free(void);
void example_05_linked_list_leak(void);
void example_06_canary_values(void);
void example_07_memory_fill_patterns(void);
void example_08_safe_free_macro(void);
void example_09_ownership_semantics(void);
void example_10_arena_allocator(void);
void example_11_reference_counting(void);
void example_12_object_pool(void);

/* ==========================================================================
 * Helper Functions
 * ========================================================================== */

void print_separator(const char *title) {
    printf("\n");
    printf("══════════════════════════════════════════════════════════════════\n");
    printf("  %s\n", title);
    printf("══════════════════════════════════════════════════════════════════\n");
}

/* ==========================================================================
 * Example 1: Simple Memory Leak
 * ========================================================================== */

void example_01_simple_leak(void) {
    print_separator("Example 1: Simple Memory Leak");
    
    printf("Demonstrating a simple memory leak:\n\n");
    
    /* This function allocates memory but doesn't free it */
    /* Using tracking to show the leak */
    
    printf("Allocating 100 bytes...\n");
    char *data = TRACK_MALLOC(100);
    
    if (data) {
        strcpy(data, "This memory will be leaked!");
        printf("Data: %s\n", data);
        printf("Address: %p\n", (void *)data);
    }
    
    /* INTENTIONAL: Not freeing 'data' to demonstrate leak */
    printf("\nāš ļø  Returning without freeing memory!\n");
    printf("This allocation will appear in the final report.\n");
}

/* ==========================================================================
 * Example 2: Leak on Error Path
 * ========================================================================== */

int process_data_leaky(const char *name) {
    char *buffer1 = TRACK_MALLOC(100);
    if (!buffer1) return -1;
    
    char *buffer2 = TRACK_MALLOC(200);
    if (!buffer2) {
        /* BUG: buffer1 is leaked here! */
        return -1;
    }
    
    /* Simulate error condition */
    if (strlen(name) > 50) {
        /* BUG: Both buffers are leaked here! */
        return -1;
    }
    
    /* Normal cleanup */
    TRACK_FREE(buffer2);
    TRACK_FREE(buffer1);
    return 0;
}

int process_data_correct(const char *name) {
    int result = -1;
    char *buffer1 = NULL;
    char *buffer2 = NULL;
    
    buffer1 = TRACK_MALLOC(100);
    if (!buffer1) goto cleanup;
    
    buffer2 = TRACK_MALLOC(200);
    if (!buffer2) goto cleanup;
    
    /* Simulate processing */
    if (strlen(name) > 50) {
        goto cleanup;  /* Error, but cleanup will happen */
    }
    
    result = 0;  /* Success */
    
cleanup:
    TRACK_FREE(buffer2);  /* free(NULL) is safe */
    TRACK_FREE(buffer1);
    return result;
}

void example_02_leak_on_error_path(void) {
    print_separator("Example 2: Leak on Error Path");
    
    printf("Demonstrating leaks on error paths:\n\n");
    
    printf("1. Calling LEAKY function with error condition:\n");
    int result = process_data_leaky("This is a very long name that exceeds fifty characters limit");
    printf("   Result: %d (buffers leaked!)\n\n", result);
    
    printf("2. Calling CORRECT function with same condition:\n");
    result = process_data_correct("This is a very long name that exceeds fifty characters limit");
    printf("   Result: %d (no leaks!)\n", result);
    
    printf("\nThe difference: CORRECT version uses goto for cleanup.\n");
}

/* ==========================================================================
 * Example 3: Proper Cleanup Pattern
 * ========================================================================== */

typedef struct {
    int *data;
    char *name;
    float *values;
} ComplexResource;

ComplexResource *resource_create(const char *name, size_t data_count, 
                                  size_t value_count) {
    ComplexResource *res = TRACK_MALLOC(sizeof(ComplexResource));
    if (!res) return NULL;
    
    /* Initialize all to NULL for safe cleanup */
    res->data = NULL;
    res->name = NULL;
    res->values = NULL;
    
    res->data = TRACK_CALLOC(data_count, sizeof(int));
    if (!res->data) goto error;
    
    res->name = TRACK_MALLOC(strlen(name) + 1);
    if (!res->name) goto error;
    strcpy(res->name, name);
    
    res->values = TRACK_CALLOC(value_count, sizeof(float));
    if (!res->values) goto error;
    
    return res;
    
error:
    /* Clean up anything that was allocated */
    TRACK_FREE(res->values);
    TRACK_FREE(res->name);
    TRACK_FREE(res->data);
    TRACK_FREE(res);
    return NULL;
}

void resource_destroy(ComplexResource *res) {
    if (res) {
        TRACK_FREE(res->values);
        TRACK_FREE(res->name);
        TRACK_FREE(res->data);
        TRACK_FREE(res);
    }
}

void example_03_leak_proper_cleanup(void) {
    print_separator("Example 3: Proper Cleanup Pattern");
    
    printf("Demonstrating proper resource cleanup:\n\n");
    
    ComplexResource *res = resource_create("TestResource", 10, 5);
    
    if (res) {
        printf("Resource created successfully:\n");
        printf("  Name: %s\n", res->name);
        printf("  Data array: %p\n", (void *)res->data);
        printf("  Values array: %p\n", (void *)res->values);
        
        /* Use the resource... */
        res->data[0] = 42;
        res->values[0] = 3.14f;
        
        /* Clean up properly */
        resource_destroy(res);
        printf("\nResource destroyed successfully.\n");
    } else {
        printf("Resource creation failed.\n");
    }
}

/* ==========================================================================
 * Example 4: Reassigning Pointer Without Freeing
 * ========================================================================== */

void example_04_reassign_without_free(void) {
    print_separator("Example 4: Reassigning Pointer Without Freeing");
    
    printf("Common mistake: reassigning pointer without freeing original.\n\n");
    
    /* WRONG WAY - demonstrated */
    printf("WRONG way (first allocation leaked):\n");
    char *str = TRACK_MALLOC(50);
    strcpy(str, "First allocation");
    printf("  str = '%s' at %p\n", str, (void *)str);
    
    /* This line causes a leak! */
    str = TRACK_MALLOC(100);  /* First 50 bytes now leaked! */
    strcpy(str, "Second allocation");
    printf("  str = '%s' at %p\n", str, (void *)str);
    
    TRACK_FREE(str);  /* Only frees the second allocation */
    printf("  After free, first 50 bytes still leaked!\n");
    
    /* CORRECT WAY */
    printf("\nCORRECT way (free before reassigning):\n");
    str = TRACK_MALLOC(50);
    strcpy(str, "First allocation");
    printf("  str = '%s' at %p\n", str, (void *)str);
    
    TRACK_FREE(str);  /* Free first before getting new memory */
    str = TRACK_MALLOC(100);
    strcpy(str, "Second allocation");
    printf("  str = '%s' at %p\n", str, (void *)str);
    
    TRACK_FREE(str);  /* Properly freed */
    printf("  Both allocations properly managed!\n");
}

/* ==========================================================================
 * Example 5: Linked List Memory Leak
 * ========================================================================== */

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

Node *create_list(int count) {
    if (count <= 0) return NULL;
    
    Node *head = TRACK_MALLOC(sizeof(Node));
    if (!head) return NULL;
    head->value = 0;
    head->next = NULL;
    
    Node *current = head;
    for (int i = 1; i < count; i++) {
        current->next = TRACK_MALLOC(sizeof(Node));
        if (!current->next) {
            /* Should free previous nodes here - simplified for demo */
            return head;
        }
        current = current->next;
        current->value = i;
        current->next = NULL;
    }
    
    return head;
}

void free_list_wrong(Node *head) {
    /* WRONG: Only frees the head node! */
    TRACK_FREE(head);  /* All other nodes leaked! */
}

void free_list_correct(Node *head) {
    /* CORRECT: Free each node */
    while (head) {
        Node *next = head->next;
        TRACK_FREE(head);
        head = next;
    }
}

void example_05_linked_list_leak(void) {
    print_separator("Example 5: Linked List Memory Leak");
    
    printf("Demonstrating linked list cleanup:\n\n");
    
    /* Create list that will be freed wrong */
    printf("Creating list 1 (will be freed incorrectly):\n");
    Node *list1 = create_list(5);
    Node *curr = list1;
    while (curr) {
        printf("  Node value: %d at %p\n", curr->value, (void *)curr);
        curr = curr->next;
    }
    
    printf("\nFreeing list 1 WRONG way (only head freed):\n");
    free_list_wrong(list1);
    printf("  4 nodes leaked!\n");
    
    /* Create list that will be freed correctly */
    printf("\nCreating list 2 (will be freed correctly):\n");
    Node *list2 = create_list(5);
    curr = list2;
    while (curr) {
        printf("  Node value: %d at %p\n", curr->value, (void *)curr);
        curr = curr->next;
    }
    
    printf("\nFreeing list 2 CORRECT way (all nodes freed):\n");
    free_list_correct(list2);
    printf("  All nodes freed!\n");
}

/* ==========================================================================
 * Example 6: Canary Values for Overflow Detection
 * ========================================================================== */

#define CANARY_VALUE 0xDEADBEEF

typedef struct {
    uint32_t front_canary;
    size_t size;
    char data[];  /* Flexible array member */
} CanaryBlock;

void *canary_alloc(size_t size) {
    size_t total = sizeof(CanaryBlock) + size + sizeof(uint32_t);
    CanaryBlock *block = malloc(total);
    if (!block) return NULL;
    
    block->front_canary = CANARY_VALUE;
    block->size = size;
    *(uint32_t *)(block->data + size) = CANARY_VALUE;  /* Back canary */
    
    return block->data;
}

int canary_check(void *ptr) {
    if (!ptr) return 0;
    
    CanaryBlock *block = (CanaryBlock *)((char *)ptr - offsetof(CanaryBlock, data));
    
    int ok = 1;
    
    if (block->front_canary != CANARY_VALUE) {
        printf("  āŒ Front canary corrupted! (buffer underflow detected)\n");
        ok = 0;
    }
    
    uint32_t back = *(uint32_t *)(block->data + block->size);
    if (back != CANARY_VALUE) {
        printf("  āŒ Back canary corrupted! (buffer overflow detected)\n");
        ok = 0;
    }
    
    if (ok) {
        printf("  āœ… Canaries intact - no overflow detected\n");
    }
    
    return ok;
}

void canary_free(void *ptr) {
    if (!ptr) return;
    CanaryBlock *block = (CanaryBlock *)((char *)ptr - offsetof(CanaryBlock, data));
    free(block);
}

void example_06_canary_values(void) {
    print_separator("Example 6: Canary Values for Overflow Detection");
    
    printf("Using canary values to detect buffer overflow:\n\n");
    
    /* Allocate with canaries */
    char *buffer = canary_alloc(10);
    if (!buffer) {
        printf("Allocation failed!\n");
        return;
    }
    
    printf("1. Initial state (no corruption):\n");
    canary_check(buffer);
    
    printf("\n2. Writing within bounds:\n");
    strcpy(buffer, "Hello");  /* 5 chars + null, fits in 10 */
    canary_check(buffer);
    
    printf("\n3. Simulating overflow (writing beyond buffer):\n");
    /* In real code, this would be a bug. We simulate by writing directly. */
    printf("   [Simulating: would write past buffer end]\n");
    /* Uncomment to see detection: */
    /* strcpy(buffer, "This string is too long!"); */
    /* canary_check(buffer); */
    printf("   Canary system would detect this corruption!\n");
    
    canary_free(buffer);
    printf("\n");
}

/* ==========================================================================
 * Example 7: Memory Fill Patterns
 * ========================================================================== */

#define ALLOC_PATTERN  0xAA  /* Newly allocated */
#define FREE_PATTERN   0xDD  /* Freed memory */

void *pattern_alloc(size_t size) {
    void *ptr = malloc(size);
    if (ptr) {
        memset(ptr, ALLOC_PATTERN, size);
    }
    return ptr;
}

void pattern_free(void *ptr, size_t size) {
    if (ptr) {
        memset(ptr, FREE_PATTERN, size);
        free(ptr);
    }
}

void example_07_memory_fill_patterns(void) {
    print_separator("Example 7: Memory Fill Patterns");
    
    printf("Using fill patterns to detect issues:\n\n");
    
    size_t size = 8;
    int *arr = pattern_alloc(size * sizeof(int));
    
    printf("1. After allocation (pattern 0x%02X):\n", ALLOC_PATTERN);
    printf("   ");
    for (size_t i = 0; i < size; i++) {
        printf("0x%08X ", (unsigned int)arr[i]);
    }
    printf("\n   These values indicate uninitialized memory!\n");
    
    printf("\n2. After proper initialization:\n");
    for (size_t i = 0; i < size; i++) {
        arr[i] = (int)(i * 10);
    }
    printf("   ");
    for (size_t i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    /* Save pointer for demonstration (don't do this in real code!) */
    int *old_arr = arr;
    
    printf("\n3. After free (pattern 0x%02X):\n", FREE_PATTERN);
    pattern_free(arr, size * sizeof(int));
    printf("   If accessed after free, would see 0x%02X%02X%02X%02X pattern\n",
           FREE_PATTERN, FREE_PATTERN, FREE_PATTERN, FREE_PATTERN);
    printf("   This helps detect use-after-free bugs!\n");
    
    (void)old_arr;  /* Suppress unused warning */
}

/* ==========================================================================
 * Example 8: Safe Free Macro
 * ========================================================================== */

/* Macro that frees and sets to NULL */
#define SAFE_FREE(ptr) do { \
    TRACK_FREE(ptr);        \
    (ptr) = NULL;           \
} while(0)

void example_08_safe_free_macro(void) {
    print_separator("Example 8: Safe Free Macro");
    
    printf("Using SAFE_FREE macro to prevent use-after-free:\n\n");
    
    int *ptr = TRACK_MALLOC(sizeof(int));
    *ptr = 42;
    printf("Before free: ptr = %p, *ptr = %d\n", (void *)ptr, *ptr);
    
    /* Normal free - pointer still has old value */
    TRACK_FREE(ptr);
    printf("After normal free: ptr = %p (dangling!)\n", (void *)ptr);
    
    /* Re-allocate for safe free demo */
    ptr = TRACK_MALLOC(sizeof(int));
    *ptr = 100;
    printf("\nBefore SAFE_FREE: ptr = %p, *ptr = %d\n", (void *)ptr, *ptr);
    
    SAFE_FREE(ptr);
    printf("After SAFE_FREE: ptr = %p (safely NULL)\n", (void *)ptr);
    
    /* This is now safe! */
    if (ptr == NULL) {
        printf("\nSafe to check: ptr is NULL, won't accidentally use it.\n");
    }
    
    /* Double free is prevented */
    SAFE_FREE(ptr);  /* free(NULL) is safe */
    printf("Double SAFE_FREE: No crash because ptr was NULL.\n");
}

/* ==========================================================================
 * Example 9: Ownership Semantics
 * ========================================================================== */

/* Functions that TRANSFER ownership (caller must free) */
char *create_message(const char *name) {  /* Returns allocated memory */
    char *msg = TRACK_MALLOC(strlen(name) + 20);
    if (msg) {
        sprintf(msg, "Hello, %s!", name);
    }
    return msg;  /* Ownership transfers to caller */
}

/* Functions that BORROW (don't free) */
void print_message(const char *msg) {  /* Borrows, doesn't own */
    printf("Message: %s\n", msg);
    /* Does NOT free msg */
}

/* Functions that TAKE ownership (will free) */
void consume_message(char *msg) {  /* Takes ownership */
    printf("Consuming: %s\n", msg);
    TRACK_FREE(msg);  /* Function takes ownership and frees */
}

void example_09_ownership_semantics(void) {
    print_separator("Example 9: Ownership Semantics");
    
    printf("Demonstrating clear ownership patterns:\n\n");
    
    /* Pattern 1: Caller creates and destroys */
    printf("1. Caller owns the memory:\n");
    char *msg1 = create_message("Alice");
    print_message(msg1);  /* Borrow - doesn't free */
    print_message(msg1);  /* Can use again */
    TRACK_FREE(msg1);     /* Caller frees */
    printf("   Caller freed the memory.\n");
    
    /* Pattern 2: Transfer ownership */
    printf("\n2. Transfer ownership:\n");
    char *msg2 = create_message("Bob");
    printf("   Created: %s\n", msg2);
    consume_message(msg2);  /* Transfers ownership, function frees */
    msg2 = NULL;  /* Best practice: set to NULL after giving away */
    printf("   Ownership transferred and freed by callee.\n");
    
    printf("\nKey rules:\n");
    printf("  - Document who owns the memory\n");
    printf("  - 'create' functions usually transfer ownership\n");
    printf("  - 'borrow' functions don't free\n");
    printf("  - 'consume' functions take ownership and free\n");
}

/* ==========================================================================
 * Example 10: Arena Allocator
 * ========================================================================== */

typedef struct {
    char *buffer;
    size_t size;
    size_t used;
} Arena;

Arena *arena_create(size_t size) {
    Arena *arena = malloc(sizeof(Arena));
    if (!arena) return NULL;
    
    arena->buffer = malloc(size);
    if (!arena->buffer) {
        free(arena);
        return NULL;
    }
    
    arena->size = size;
    arena->used = 0;
    return arena;
}

void *arena_alloc(Arena *arena, size_t size) {
    /* Align to 8 bytes */
    size_t aligned_size = (size + 7) & ~(size_t)7;
    
    if (arena->used + aligned_size > arena->size) {
        return NULL;  /* Out of space */
    }
    
    void *ptr = arena->buffer + arena->used;
    arena->used += aligned_size;
    return ptr;
}

void arena_reset(Arena *arena) {
    arena->used = 0;  /* "Free" everything at once */
}

void arena_destroy(Arena *arena) {
    if (arena) {
        free(arena->buffer);
        free(arena);
    }
}

void example_10_arena_allocator(void) {
    print_separator("Example 10: Arena Allocator");
    
    printf("Arena allocator - no individual frees needed:\n\n");
    
    Arena *arena = arena_create(1024);
    if (!arena) {
        printf("Failed to create arena\n");
        return;
    }
    
    printf("Arena created with %zu bytes\n\n", arena->size);
    
    /* Allocate many objects */
    printf("Allocating objects from arena:\n");
    int *nums[10];
    for (int i = 0; i < 10; i++) {
        nums[i] = arena_alloc(arena, sizeof(int));
        if (nums[i]) {
            *nums[i] = i * 100;
            printf("  nums[%d] = %d (arena used: %zu)\n", i, *nums[i], arena->used);
        }
    }
    
    char *str = arena_alloc(arena, 50);
    if (str) {
        strcpy(str, "Hello from arena!");
        printf("\n  str = '%s' (arena used: %zu)\n", str, arena->used);
    }
    
    /* No individual frees! Just reset or destroy */
    printf("\nResetting arena (frees all at once)...\n");
    arena_reset(arena);
    printf("Arena used: %zu (all freed!)\n", arena->used);
    
    /* Can reuse */
    printf("\nReallocating after reset:\n");
    int *new_num = arena_alloc(arena, sizeof(int));
    if (new_num) {
        *new_num = 42;
        printf("  Reallocated: %d\n", *new_num);
    }
    
    arena_destroy(arena);
    printf("\nArena destroyed.\n");
}

/* ==========================================================================
 * Example 11: Reference Counting
 * ========================================================================== */

typedef struct {
    int ref_count;
    char *data;
} RefString;

RefString *refstring_create(const char *str) {
    RefString *rs = TRACK_MALLOC(sizeof(RefString));
    if (!rs) return NULL;
    
    rs->data = TRACK_MALLOC(strlen(str) + 1);
    if (!rs->data) {
        TRACK_FREE(rs);
        return NULL;
    }
    
    strcpy(rs->data, str);
    rs->ref_count = 1;
    return rs;
}

RefString *refstring_acquire(RefString *rs) {
    if (rs) {
        rs->ref_count++;
    }
    return rs;
}

void refstring_release(RefString *rs) {
    if (rs) {
        rs->ref_count--;
        if (rs->ref_count <= 0) {
            TRACK_FREE(rs->data);
            TRACK_FREE(rs);
        }
    }
}

void example_11_reference_counting(void) {
    print_separator("Example 11: Reference Counting");
    
    printf("Reference counting for shared ownership:\n\n");
    
    RefString *str = refstring_create("Shared String");
    printf("Created: '%s', ref_count = %d\n", str->data, str->ref_count);
    
    /* Share the string */
    RefString *ref1 = refstring_acquire(str);
    printf("After acquire: ref_count = %d\n", str->ref_count);
    
    RefString *ref2 = refstring_acquire(str);
    printf("After another acquire: ref_count = %d\n", str->ref_count);
    
    /* Release references */
    printf("\nReleasing references:\n");
    refstring_release(ref2);
    printf("  After release: ref_count = %d\n", str->ref_count);
    
    refstring_release(ref1);
    printf("  After release: ref_count = %d\n", str->ref_count);
    
    /* Last release frees the memory */
    printf("  Final release (will free memory):\n");
    refstring_release(str);
    printf("  Memory freed!\n");
}

/* ==========================================================================
 * Example 12: Object Pool
 * ========================================================================== */

#define POOL_SIZE 8

typedef struct {
    int value;
    int in_use;
} PoolObject;

typedef struct {
    PoolObject objects[POOL_SIZE];
    int free_count;
} ObjectPool;

ObjectPool *pool_create(void) {
    ObjectPool *pool = calloc(1, sizeof(ObjectPool));
    if (pool) {
        pool->free_count = POOL_SIZE;
    }
    return pool;
}

PoolObject *pool_acquire(ObjectPool *pool) {
    if (pool->free_count == 0) return NULL;
    
    for (int i = 0; i < POOL_SIZE; i++) {
        if (!pool->objects[i].in_use) {
            pool->objects[i].in_use = 1;
            pool->objects[i].value = 0;
            pool->free_count--;
            return &pool->objects[i];
        }
    }
    return NULL;
}

void pool_release(ObjectPool *pool, PoolObject *obj) {
    if (obj && obj->in_use) {
        obj->in_use = 0;
        pool->free_count++;
    }
}

void pool_destroy(ObjectPool *pool) {
    free(pool);
}

void example_12_object_pool(void) {
    print_separator("Example 12: Object Pool");
    
    printf("Object pool for fixed-size allocations:\n\n");
    
    ObjectPool *pool = pool_create();
    if (!pool) {
        printf("Failed to create pool\n");
        return;
    }
    
    printf("Pool created, free slots: %d\n\n", pool->free_count);
    
    /* Acquire objects */
    PoolObject *objs[5];
    printf("Acquiring 5 objects:\n");
    for (int i = 0; i < 5; i++) {
        objs[i] = pool_acquire(pool);
        if (objs[i]) {
            objs[i]->value = (i + 1) * 10;
            printf("  Object %d: value=%d, free_count=%d\n", 
                   i, objs[i]->value, pool->free_count);
        }
    }
    
    /* Release some objects */
    printf("\nReleasing objects 0 and 2:\n");
    pool_release(pool, objs[0]);
    pool_release(pool, objs[2]);
    printf("  Free count: %d\n", pool->free_count);
    
    /* Reuse */
    printf("\nAcquiring 2 new objects (reusing slots):\n");
    PoolObject *new1 = pool_acquire(pool);
    PoolObject *new2 = pool_acquire(pool);
    if (new1) {
        new1->value = 999;
        printf("  New object: value=%d\n", new1->value);
    }
    if (new2) {
        new2->value = 888;
        printf("  New object: value=%d\n", new2->value);
    }
    
    printf("\nFinal free count: %d\n", pool->free_count);
    
    pool_destroy(pool);
    printf("Pool destroyed (all objects freed at once).\n");
}

/* ==========================================================================
 * Main Function
 * ========================================================================== */

int main(void) {
    printf("\n");
    printf("╔══════════════════════════════════════════════════════════════════╗\n");
    printf("ā•‘        Memory Leaks and Debugging in C - Examples                ā•‘\n");
    printf("ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n");
    
    example_01_simple_leak();
    example_02_leak_on_error_path();
    example_03_leak_proper_cleanup();
    example_04_reassign_without_free();
    example_05_linked_list_leak();
    example_06_canary_values();
    example_07_memory_fill_patterns();
    example_08_safe_free_macro();
    example_09_ownership_semantics();
    example_10_arena_allocator();
    example_11_reference_counting();
    example_12_object_pool();
    
    /* Print final memory report */
    print_memory_report();
    
    printf("╔══════════════════════════════════════════════════════════════════╗\n");
    printf("ā•‘                    All Examples Complete!                        ā•‘\n");
    printf("ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n\n");
    
    return 0;
}

/* End of examples.c */
Examples - C Programming Tutorial | DeepML