c
exercises
exercises.c🔧c
/**
* =============================================================================
* Memory Leaks and Debugging in C - Exercises
* =============================================================================
*
* Practice exercises for memory debugging and leak prevention.
*
* Compilation: gcc -g -O0 -o exercises exercises.c -Wall -Wextra
*
* For Valgrind testing:
* gcc -g -O0 -o exercises exercises.c
* valgrind --leak-check=full ./exercises
*
* Topics covered:
* - Finding and fixing memory leaks
* - Proper cleanup patterns
* - Memory debugging wrappers
* - Safe memory practices
* =============================================================================
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* ==========================================================================
* Exercise 1: Find the Memory Leak
* ==========================================================================
* The function below has a memory leak. Find and fix it.
*
* Expected Output:
* Array sum: 55
* Memory properly freed!
*/
int calculate_sum_buggy(int n) {
int *arr = malloc(n * sizeof(int));
if (!arr) return -1;
for (int i = 0; i < n; i++) {
arr[i] = i + 1;
}
int sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
}
return sum; /* BUG: Memory leaked here! */
}
/* TODO: Write the fixed version */
int calculate_sum_fixed(int n) {
/* Your implementation here */
return 0;
}
void exercise_01(void) {
printf("Exercise 1: Find the Memory Leak\n");
printf("---------------------------------\n");
/* Uncomment to test your fixed version */
/*
int sum = calculate_sum_fixed(10);
printf("Array sum: %d\n", sum);
printf("Memory properly freed!\n");
*/
printf("TODO: Implement calculate_sum_fixed()\n");
printf("\n");
}
/* ==========================================================================
* Exercise 2: Fix Early Return Leak
* ==========================================================================
* This function has multiple memory leaks on error paths.
* Fix all the leaks using proper cleanup.
*
* Expected Output:
* Error handled with no memory leaks!
*/
typedef struct {
char *name;
int *data;
float *values;
} Resource;
Resource *create_resource_buggy(const char *name, int data_count,
int value_count) {
Resource *res = malloc(sizeof(Resource));
if (!res) return NULL;
res->name = malloc(strlen(name) + 1);
if (!res->name) {
return NULL; /* BUG: res leaked! */
}
strcpy(res->name, name);
res->data = malloc(data_count * sizeof(int));
if (!res->data) {
return NULL; /* BUG: res and res->name leaked! */
}
res->values = malloc(value_count * sizeof(float));
if (!res->values) {
return NULL; /* BUG: res, res->name, res->data leaked! */
}
return res;
}
/* TODO: Write the fixed version */
Resource *create_resource_fixed(const char *name, int data_count,
int value_count) {
/* Your implementation here */
return NULL;
}
void destroy_resource(Resource *res) {
/* TODO: Implement proper cleanup */
}
void exercise_02(void) {
printf("Exercise 2: Fix Early Return Leak\n");
printf("----------------------------------\n");
/* Uncomment to test your fixed version */
/*
Resource *res = create_resource_fixed("Test", 10, 5);
if (res) {
printf("Resource created successfully\n");
destroy_resource(res);
printf("Resource destroyed properly!\n");
}
*/
printf("TODO: Implement create_resource_fixed() and destroy_resource()\n");
printf("\n");
}
/* ==========================================================================
* Exercise 3: Implement Memory Tracking
* ==========================================================================
* Implement simple memory tracking functions that count allocations
* and detect leaks.
*
* Expected Output:
* Allocated 3 blocks
* Freed 3 blocks
* No leaks detected!
*/
/* Global counters */
static int g_alloc_count = 0;
static int g_free_count = 0;
/* TODO: Implement these functions */
void *tracked_malloc(size_t size) {
/* Allocate memory and increment counter */
return NULL;
}
void tracked_free(void *ptr) {
/* Free memory and increment counter */
}
void print_tracking_report(void) {
/* Print allocation/free counts and detect leaks */
}
void exercise_03(void) {
printf("Exercise 3: Implement Memory Tracking\n");
printf("--------------------------------------\n");
/* Uncomment to test your implementation */
/*
int *a = tracked_malloc(sizeof(int));
int *b = tracked_malloc(sizeof(int));
int *c = tracked_malloc(sizeof(int));
tracked_free(a);
tracked_free(b);
tracked_free(c);
print_tracking_report();
*/
printf("TODO: Implement tracked_malloc(), tracked_free(), print_tracking_report()\n");
printf("\n");
}
/* ==========================================================================
* Exercise 4: Linked List Cleanup
* ==========================================================================
* The given linked list cleanup function is buggy. Fix it.
*
* Expected Output:
* Created list: 1 -> 2 -> 3 -> 4 -> 5 -> NULL
* All nodes freed successfully!
*/
typedef struct ListNode {
int value;
struct ListNode *next;
} ListNode;
ListNode *create_list(int count) {
if (count <= 0) return NULL;
ListNode *head = malloc(sizeof(ListNode));
if (!head) return NULL;
head->value = 1;
head->next = NULL;
ListNode *current = head;
for (int i = 2; i <= count; i++) {
current->next = malloc(sizeof(ListNode));
if (!current->next) return head; /* Partial list */
current = current->next;
current->value = i;
current->next = NULL;
}
return head;
}
void print_list(ListNode *head) {
while (head) {
printf("%d -> ", head->value);
head = head->next;
}
printf("NULL\n");
}
/* BUGGY: This function has problems */
void free_list_buggy(ListNode *head) {
while (head) {
free(head); /* BUG: head->next lost! */
head = head->next; /* Accessing freed memory! */
}
}
/* TODO: Write the fixed version */
void free_list_fixed(ListNode *head) {
/* Your implementation here */
}
void exercise_04(void) {
printf("Exercise 4: Linked List Cleanup\n");
printf("--------------------------------\n");
/* Uncomment to test your fixed version */
/*
ListNode *list = create_list(5);
printf("Created list: ");
print_list(list);
free_list_fixed(list);
printf("All nodes freed successfully!\n");
*/
printf("TODO: Implement free_list_fixed()\n");
printf("\n");
}
/* ==========================================================================
* Exercise 5: Safe Free Macro
* ==========================================================================
* Implement a SAFE_FREE macro that frees memory and sets pointer to NULL.
*
* Expected Output:
* Before free: ptr = 0x...
* After SAFE_FREE: ptr = (nil)
* Double free safe: no crash!
*/
/* TODO: Define SAFE_FREE macro */
/* #define SAFE_FREE(ptr) ... */
void exercise_05(void) {
printf("Exercise 5: Safe Free Macro\n");
printf("----------------------------\n");
/* Uncomment to test your macro */
/*
int *ptr = malloc(sizeof(int));
*ptr = 42;
printf("Before free: ptr = %p\n", (void *)ptr);
SAFE_FREE(ptr);
printf("After SAFE_FREE: ptr = %p\n", (void *)ptr);
// This should be safe now
SAFE_FREE(ptr);
printf("Double free safe: no crash!\n");
*/
printf("TODO: Define SAFE_FREE macro\n");
printf("\n");
}
/* ==========================================================================
* Exercise 6: String Concatenation Without Leaks
* ==========================================================================
* Implement a function that concatenates multiple strings without leaks.
*
* Expected Output:
* Result: Hello World!
* Memory freed properly!
*/
/* BUGGY version - has leaks */
char *concat_strings_buggy(const char *s1, const char *s2, const char *s3) {
char *temp = malloc(strlen(s1) + strlen(s2) + 1);
strcpy(temp, s1);
strcat(temp, s2);
char *result = malloc(strlen(temp) + strlen(s3) + 1);
strcpy(result, temp);
strcat(result, s3);
return result; /* BUG: temp is leaked! */
}
/* TODO: Write the fixed version */
char *concat_strings_fixed(const char *s1, const char *s2, const char *s3) {
/* Your implementation here */
return NULL;
}
void exercise_06(void) {
printf("Exercise 6: String Concatenation Without Leaks\n");
printf("-----------------------------------------------\n");
/* Uncomment to test your fixed version */
/*
char *result = concat_strings_fixed("Hello", " ", "World!");
if (result) {
printf("Result: %s\n", result);
free(result);
printf("Memory freed properly!\n");
}
*/
printf("TODO: Implement concat_strings_fixed()\n");
printf("\n");
}
/* ==========================================================================
* Exercise 7: Implement Arena Allocator
* ==========================================================================
* Implement a simple arena allocator that allocates from a fixed buffer.
* No individual frees - just reset or destroy the whole arena.
*
* Expected Output:
* Allocated 3 integers from arena
* Arena used: 24 bytes
* Arena reset, used: 0 bytes
*/
typedef struct {
char *buffer;
size_t size;
size_t used;
} SimpleArena;
/* TODO: Implement these functions */
SimpleArena *arena_create(size_t size) {
return NULL;
}
void *arena_alloc(SimpleArena *arena, size_t size) {
return NULL;
}
void arena_reset(SimpleArena *arena) {
}
void arena_destroy(SimpleArena *arena) {
}
void exercise_07(void) {
printf("Exercise 7: Implement Arena Allocator\n");
printf("--------------------------------------\n");
/* Uncomment to test your implementation */
/*
SimpleArena *arena = arena_create(1024);
if (!arena) {
printf("Failed to create arena\n");
return;
}
int *a = arena_alloc(arena, sizeof(int));
int *b = arena_alloc(arena, sizeof(int));
int *c = arena_alloc(arena, sizeof(int));
if (a && b && c) {
*a = 10; *b = 20; *c = 30;
printf("Allocated 3 integers from arena\n");
printf("Arena used: %zu bytes\n", arena->used);
}
arena_reset(arena);
printf("Arena reset, used: %zu bytes\n", arena->used);
arena_destroy(arena);
*/
printf("TODO: Implement arena functions\n");
printf("\n");
}
/* ==========================================================================
* Exercise 8: Fix the Double Free
* ==========================================================================
* The code below has a double-free bug. Find and fix it.
*
* Expected Output:
* Array processed successfully!
* No double-free occurred!
*/
void process_array_buggy(int *arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2;
}
free(arr); /* BUG: Function shouldn't free caller's memory */
}
/* TODO: Write the fixed version (decide on ownership) */
void process_array_fixed(int *arr, int size) {
/* Your implementation here */
}
void exercise_08(void) {
printf("Exercise 8: Fix the Double Free\n");
printf("---------------------------------\n");
/* Uncomment to test your fixed version */
/*
int *arr = malloc(5 * sizeof(int));
for (int i = 0; i < 5; i++) arr[i] = i + 1;
process_array_fixed(arr, 5);
printf("Array processed successfully!\n");
free(arr); // Caller frees (or remove if function should own it)
printf("No double-free occurred!\n");
*/
printf("TODO: Implement process_array_fixed() with clear ownership\n");
printf("\n");
}
/* ==========================================================================
* Exercise 9: Implement Reference Counting
* ==========================================================================
* Implement reference counting for a shared buffer.
*
* Expected Output:
* Created shared buffer, refs=1
* After acquire, refs=2
* After release, refs=1
* After final release: buffer freed
*/
typedef struct {
char *data;
int ref_count;
} SharedBuffer;
/* TODO: Implement these functions */
SharedBuffer *sharedbuf_create(const char *initial_data) {
return NULL;
}
SharedBuffer *sharedbuf_acquire(SharedBuffer *buf) {
return NULL;
}
void sharedbuf_release(SharedBuffer *buf) {
}
void exercise_09(void) {
printf("Exercise 9: Implement Reference Counting\n");
printf("-----------------------------------------\n");
/* Uncomment to test your implementation */
/*
SharedBuffer *buf = sharedbuf_create("Shared Data");
printf("Created shared buffer, refs=%d\n", buf->ref_count);
SharedBuffer *ref = sharedbuf_acquire(buf);
printf("After acquire, refs=%d\n", buf->ref_count);
sharedbuf_release(ref);
printf("After release, refs=%d\n", buf->ref_count);
sharedbuf_release(buf);
printf("After final release: buffer freed\n");
*/
printf("TODO: Implement reference counting functions\n");
printf("\n");
}
/* ==========================================================================
* Exercise 10: Memory Pool Implementation
* ==========================================================================
* Implement a fixed-size object pool.
*
* Expected Output:
* Pool created with 8 slots
* Acquired 3 objects
* Released 1 object
* Free slots: 6
*/
#define POOL_SIZE 8
typedef struct {
int data;
int in_use;
} PoolItem;
typedef struct {
PoolItem items[POOL_SIZE];
int free_count;
} ItemPool;
/* TODO: Implement these functions */
ItemPool *pool_create(void) {
return NULL;
}
PoolItem *pool_acquire(ItemPool *pool) {
return NULL;
}
void pool_release(ItemPool *pool, PoolItem *item) {
}
void pool_destroy(ItemPool *pool) {
}
void exercise_10(void) {
printf("Exercise 10: Memory Pool Implementation\n");
printf("----------------------------------------\n");
/* Uncomment to test your implementation */
/*
ItemPool *pool = pool_create();
printf("Pool created with %d slots\n", POOL_SIZE);
PoolItem *a = pool_acquire(pool);
PoolItem *b = pool_acquire(pool);
PoolItem *c = pool_acquire(pool);
printf("Acquired 3 objects\n");
pool_release(pool, b);
printf("Released 1 object\n");
printf("Free slots: %d\n", pool->free_count);
pool_release(pool, a);
pool_release(pool, c);
pool_destroy(pool);
*/
printf("TODO: Implement pool functions\n");
printf("\n");
}
/* ==========================================================================
* Main Function
* ========================================================================== */
int main(void) {
printf("\n");
printf("╔══════════════════════════════════════════════════════════════════╗\n");
printf("║ Memory Leaks and Debugging in C - Exercises ║\n");
printf("╚══════════════════════════════════════════════════════════════════╝\n\n");
exercise_01();
exercise_02();
exercise_03();
exercise_04();
exercise_05();
exercise_06();
exercise_07();
exercise_08();
exercise_09();
exercise_10();
printf("╔══════════════════════════════════════════════════════════════════╗\n");
printf("║ Exercises Complete! ║\n");
printf("╚══════════════════════════════════════════════════════════════════╝\n\n");
return 0;
}
/* ==========================================================================
* ANSWER KEY
* ==========================================================================
*
* Exercise 1: Find the Memory Leak
* ---------------------------------
* int calculate_sum_fixed(int n) {
* int *arr = malloc(n * sizeof(int));
* if (!arr) return -1;
*
* for (int i = 0; i < n; i++) {
* arr[i] = i + 1;
* }
*
* int sum = 0;
* for (int i = 0; i < n; i++) {
* sum += arr[i];
* }
*
* free(arr); // Fix: free before return
* return sum;
* }
*
* Exercise 2: Fix Early Return Leak
* ----------------------------------
* Resource *create_resource_fixed(const char *name, int data_count,
* int value_count) {
* Resource *res = malloc(sizeof(Resource));
* if (!res) return NULL;
*
* res->name = NULL;
* res->data = NULL;
* res->values = NULL;
*
* res->name = malloc(strlen(name) + 1);
* if (!res->name) goto cleanup;
* strcpy(res->name, name);
*
* res->data = malloc(data_count * sizeof(int));
* if (!res->data) goto cleanup;
*
* res->values = malloc(value_count * sizeof(float));
* if (!res->values) goto cleanup;
*
* return res;
*
* cleanup:
* free(res->values);
* free(res->data);
* free(res->name);
* free(res);
* return NULL;
* }
*
* void destroy_resource(Resource *res) {
* if (res) {
* free(res->values);
* free(res->data);
* free(res->name);
* free(res);
* }
* }
*
* Exercise 3: Implement Memory Tracking
* --------------------------------------
* void *tracked_malloc(size_t size) {
* void *ptr = malloc(size);
* if (ptr) g_alloc_count++;
* return ptr;
* }
*
* void tracked_free(void *ptr) {
* if (ptr) {
* g_free_count++;
* free(ptr);
* }
* }
*
* void print_tracking_report(void) {
* printf("Allocated %d blocks\n", g_alloc_count);
* printf("Freed %d blocks\n", g_free_count);
* if (g_alloc_count == g_free_count) {
* printf("No leaks detected!\n");
* } else {
* printf("WARNING: %d blocks leaked!\n", g_alloc_count - g_free_count);
* }
* }
*
* Exercise 4: Linked List Cleanup
* --------------------------------
* void free_list_fixed(ListNode *head) {
* while (head) {
* ListNode *next = head->next; // Save next before freeing
* free(head);
* head = next;
* }
* }
*
* Exercise 5: Safe Free Macro
* ----------------------------
* #define SAFE_FREE(ptr) do { \
* free(ptr); \
* (ptr) = NULL; \
* } while(0)
*
* Exercise 6: String Concatenation Without Leaks
* -----------------------------------------------
* char *concat_strings_fixed(const char *s1, const char *s2, const char *s3) {
* size_t len = strlen(s1) + strlen(s2) + strlen(s3) + 1;
* char *result = malloc(len);
* if (!result) return NULL;
*
* strcpy(result, s1);
* strcat(result, s2);
* strcat(result, s3);
*
* return result;
* }
*
* // Alternative with temp:
* char *concat_strings_fixed_v2(const char *s1, const char *s2, const char *s3) {
* char *temp = malloc(strlen(s1) + strlen(s2) + 1);
* if (!temp) return NULL;
* strcpy(temp, s1);
* strcat(temp, s2);
*
* char *result = malloc(strlen(temp) + strlen(s3) + 1);
* if (!result) {
* free(temp);
* return NULL;
* }
* strcpy(result, temp);
* strcat(result, s3);
*
* free(temp); // Fix: free the temporary
* return result;
* }
*
* Exercise 7: Implement Arena Allocator
* --------------------------------------
* SimpleArena *arena_create(size_t size) {
* SimpleArena *arena = malloc(sizeof(SimpleArena));
* 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(SimpleArena *arena, size_t size) {
* size_t aligned = (size + 7) & ~(size_t)7; // 8-byte align
* if (arena->used + aligned > arena->size) return NULL;
*
* void *ptr = arena->buffer + arena->used;
* arena->used += aligned;
* return ptr;
* }
*
* void arena_reset(SimpleArena *arena) {
* arena->used = 0;
* }
*
* void arena_destroy(SimpleArena *arena) {
* if (arena) {
* free(arena->buffer);
* free(arena);
* }
* }
*
* Exercise 8: Fix the Double Free
* ---------------------------------
* // Option 1: Function doesn't own the memory
* void process_array_fixed(int *arr, int size) {
* for (int i = 0; i < size; i++) {
* arr[i] *= 2;
* }
* // Don't free - caller owns the memory
* }
*
* // Option 2: Function takes ownership (document clearly!)
* void process_array_and_free(int *arr, int size) {
* for (int i = 0; i < size; i++) {
* arr[i] *= 2;
* }
* free(arr); // Function takes ownership and frees
* // Caller must NOT free after this
* }
*
* Exercise 9: Implement Reference Counting
* -----------------------------------------
* SharedBuffer *sharedbuf_create(const char *initial_data) {
* SharedBuffer *buf = malloc(sizeof(SharedBuffer));
* if (!buf) return NULL;
*
* buf->data = malloc(strlen(initial_data) + 1);
* if (!buf->data) {
* free(buf);
* return NULL;
* }
*
* strcpy(buf->data, initial_data);
* buf->ref_count = 1;
* return buf;
* }
*
* SharedBuffer *sharedbuf_acquire(SharedBuffer *buf) {
* if (buf) buf->ref_count++;
* return buf;
* }
*
* void sharedbuf_release(SharedBuffer *buf) {
* if (buf) {
* buf->ref_count--;
* if (buf->ref_count <= 0) {
* free(buf->data);
* free(buf);
* }
* }
* }
*
* Exercise 10: Memory Pool Implementation
* ----------------------------------------
* ItemPool *pool_create(void) {
* ItemPool *pool = calloc(1, sizeof(ItemPool));
* if (pool) {
* pool->free_count = POOL_SIZE;
* }
* return pool;
* }
*
* PoolItem *pool_acquire(ItemPool *pool) {
* if (pool->free_count == 0) return NULL;
*
* for (int i = 0; i < POOL_SIZE; i++) {
* if (!pool->items[i].in_use) {
* pool->items[i].in_use = 1;
* pool->items[i].data = 0;
* pool->free_count--;
* return &pool->items[i];
* }
* }
* return NULL;
* }
*
* void pool_release(ItemPool *pool, PoolItem *item) {
* if (item && item->in_use) {
* item->in_use = 0;
* pool->free_count++;
* }
* }
*
* void pool_destroy(ItemPool *pool) {
* free(pool);
* }
*
* ==========================================================================
*/