c
examples
examples.cš§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 */