c

exercises

exercises.c🔧
/**
 * Dynamic Memory Allocation - Exercises
 * 
 * Practice problems for malloc, calloc, realloc, and free.
 * Each exercise includes the problem statement and solution.
 * 
 * Compile: gcc exercises.c -o exercises
 * Run: ./exercises
 */

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

/* ============================================================
 * EXERCISE 1: Basic malloc and free
 * 
 * Write a function that:
 * 1. Allocates memory for n integers using malloc
 * 2. Fills the array with values 1 to n
 * 3. Calculates and returns the sum
 * 4. Properly frees the memory
 * ============================================================ */

int exercise1_sum_n(int n) {
    // YOUR CODE HERE
    // Hints:
    // - Use malloc(n * sizeof(int))
    // - Check if allocation succeeded
    // - Fill with 1, 2, 3, ..., n
    // - Calculate sum
    // - Free memory before returning
    
    return 0;  // Replace with your implementation
}

// Solution
int solution1_sum_n(int n) {
    if (n <= 0) return 0;
    
    int *arr = (int *)malloc(n * sizeof(int));
    if (arr == NULL) {
        return -1;  // Allocation failed
    }
    
    // Fill with 1 to n
    for (int i = 0; i < n; i++) {
        arr[i] = i + 1;
    }
    
    // Calculate sum
    int sum = 0;
    for (int i = 0; i < n; i++) {
        sum += arr[i];
    }
    
    free(arr);
    return sum;
}

void test_exercise1(void) {
    printf("=== Exercise 1: Basic malloc and free ===\n\n");
    
    printf("Testing solution1_sum_n:\n");
    printf("  Sum of 1 to 5:  %d (expected: 15)\n", solution1_sum_n(5));
    printf("  Sum of 1 to 10: %d (expected: 55)\n", solution1_sum_n(10));
    printf("  Sum of 1 to 100: %d (expected: 5050)\n", solution1_sum_n(100));
    printf("\n");
}


/* ============================================================
 * EXERCISE 2: Dynamic String Duplication
 * 
 * Implement a function my_strdup that:
 * 1. Takes a string as input
 * 2. Allocates enough memory for a copy (including null terminator)
 * 3. Copies the string to the new memory
 * 4. Returns the pointer to the new string
 * 
 * Note: Caller is responsible for freeing the returned memory
 * ============================================================ */

char* exercise2_my_strdup(const char *str) {
    // YOUR CODE HERE
    // Hints:
    // - Calculate length with strlen()
    // - Allocate strlen + 1 bytes
    // - Copy with strcpy()
    // - Return the new pointer
    
    return NULL;  // Replace with your implementation
}

// Solution
char* solution2_my_strdup(const char *str) {
    if (str == NULL) return NULL;
    
    size_t len = strlen(str) + 1;  // +1 for null terminator
    char *copy = (char *)malloc(len);
    
    if (copy == NULL) return NULL;
    
    strcpy(copy, str);
    return copy;
}

void test_exercise2(void) {
    printf("=== Exercise 2: Dynamic String Duplication ===\n\n");
    
    const char *original = "Hello, Dynamic Memory!";
    char *copy = solution2_my_strdup(original);
    
    if (copy) {
        printf("Original: \"%s\" at %p\n", original, (void*)original);
        printf("Copy:     \"%s\" at %p\n", copy, (void*)copy);
        printf("Are they equal? %s\n", strcmp(original, copy) == 0 ? "Yes" : "No");
        printf("Are they the same address? %s\n", original == copy ? "Yes" : "No");
        
        free(copy);
        printf("Copy freed successfully.\n\n");
    }
}


/* ============================================================
 * EXERCISE 3: calloc for Zero-Initialized Array
 * 
 * Write a function that:
 * 1. Uses calloc to allocate an array of n doubles
 * 2. Returns the pointer to the array
 * 3. Verify that all elements are initialized to 0.0
 * ============================================================ */

double* exercise3_zero_array(int n) {
    // YOUR CODE HERE
    // Hint: Use calloc(n, sizeof(double))
    
    return NULL;  // Replace with your implementation
}

// Solution
double* solution3_zero_array(int n) {
    if (n <= 0) return NULL;
    
    double *arr = (double *)calloc(n, sizeof(double));
    return arr;  // Will be NULL if allocation failed
}

void test_exercise3(void) {
    printf("=== Exercise 3: calloc Zero-Initialization ===\n\n");
    
    int n = 5;
    double *arr = solution3_zero_array(n);
    
    if (arr) {
        printf("Allocated %d doubles with calloc:\n", n);
        printf("Values: ");
        int all_zero = 1;
        for (int i = 0; i < n; i++) {
            printf("%.1f ", arr[i]);
            if (arr[i] != 0.0) all_zero = 0;
        }
        printf("\nAll zeros? %s\n", all_zero ? "Yes" : "No");
        
        free(arr);
        printf("Memory freed.\n\n");
    }
}


/* ============================================================
 * EXERCISE 4: realloc to Grow an Array
 * 
 * Write a function that:
 * 1. Takes an existing array, its current size, and new size
 * 2. Uses realloc to resize the array
 * 3. Initializes any new elements to a given value
 * 4. Returns the new pointer (or NULL on failure)
 * ============================================================ */

int* exercise4_grow_array(int *arr, int old_size, int new_size, int init_value) {
    // YOUR CODE HERE
    // Hints:
    // - Use realloc(arr, new_size * sizeof(int))
    // - Initialize new elements from old_size to new_size-1
    // - Handle failure case
    
    return NULL;  // Replace with your implementation
}

// Solution
int* solution4_grow_array(int *arr, int old_size, int new_size, int init_value) {
    if (new_size <= 0) return NULL;
    
    int *new_arr = (int *)realloc(arr, new_size * sizeof(int));
    if (new_arr == NULL) return NULL;
    
    // Initialize new elements
    for (int i = old_size; i < new_size; i++) {
        new_arr[i] = init_value;
    }
    
    return new_arr;
}

void test_exercise4(void) {
    printf("=== Exercise 4: realloc to Grow Array ===\n\n");
    
    // Start with 3 elements
    int *arr = (int *)malloc(3 * sizeof(int));
    if (arr == NULL) return;
    
    arr[0] = 10;
    arr[1] = 20;
    arr[2] = 30;
    
    printf("Original array (3 elements): ");
    for (int i = 0; i < 3; i++) printf("%d ", arr[i]);
    printf("\n");
    
    // Grow to 6 elements, initialize new ones to 0
    arr = solution4_grow_array(arr, 3, 6, 0);
    
    if (arr) {
        printf("After growing to 6 (new elements = 0): ");
        for (int i = 0; i < 6; i++) printf("%d ", arr[i]);
        printf("\n");
        
        free(arr);
        printf("Memory freed.\n\n");
    }
}


/* ============================================================
 * EXERCISE 5: Concatenate Two Dynamic Arrays
 * 
 * Write a function that:
 * 1. Takes two arrays and their sizes
 * 2. Allocates a new array to hold all elements
 * 3. Copies both arrays into the new array
 * 4. Returns the new array (caller must free it)
 * ============================================================ */

int* exercise5_concat_arrays(int *arr1, int size1, int *arr2, int size2) {
    // YOUR CODE HERE
    // Hints:
    // - Allocate (size1 + size2) * sizeof(int)
    // - Copy arr1, then arr2
    
    return NULL;  // Replace with your implementation
}

// Solution
int* solution5_concat_arrays(int *arr1, int size1, int *arr2, int size2) {
    int total = size1 + size2;
    if (total <= 0) return NULL;
    
    int *result = (int *)malloc(total * sizeof(int));
    if (result == NULL) return NULL;
    
    // Copy first array
    for (int i = 0; i < size1; i++) {
        result[i] = arr1[i];
    }
    
    // Copy second array
    for (int i = 0; i < size2; i++) {
        result[size1 + i] = arr2[i];
    }
    
    return result;
}

void test_exercise5(void) {
    printf("=== Exercise 5: Concatenate Arrays ===\n\n");
    
    int arr1[] = {1, 2, 3};
    int arr2[] = {4, 5, 6, 7};
    
    printf("Array 1: ");
    for (int i = 0; i < 3; i++) printf("%d ", arr1[i]);
    printf("\n");
    
    printf("Array 2: ");
    for (int i = 0; i < 4; i++) printf("%d ", arr2[i]);
    printf("\n");
    
    int *result = solution5_concat_arrays(arr1, 3, arr2, 4);
    
    if (result) {
        printf("Concatenated: ");
        for (int i = 0; i < 7; i++) printf("%d ", result[i]);
        printf("\n");
        
        free(result);
        printf("Memory freed.\n\n");
    }
}


/* ============================================================
 * EXERCISE 6: Create Dynamic 2D Matrix
 * 
 * Write functions to:
 * 1. create_matrix(rows, cols) - allocate a 2D matrix
 * 2. free_matrix(matrix, rows) - free the matrix
 * 3. Fill with row*10 + col pattern
 * ============================================================ */

int** exercise6_create_matrix(int rows, int cols) {
    // YOUR CODE HERE
    // Hints:
    // - Allocate array of row pointers first
    // - Then allocate each row
    // - Handle failure (free what you've allocated)
    
    return NULL;  // Replace with your implementation
}

void exercise6_free_matrix(int **matrix, int rows) {
    // YOUR CODE HERE
    // Hint: Free each row, then free the array of pointers
}

// Solution
int** solution6_create_matrix(int rows, int cols) {
    if (rows <= 0 || cols <= 0) return NULL;
    
    int **matrix = (int **)malloc(rows * sizeof(int *));
    if (matrix == NULL) return NULL;
    
    for (int i = 0; i < rows; i++) {
        matrix[i] = (int *)malloc(cols * sizeof(int));
        if (matrix[i] == NULL) {
            // Cleanup on failure
            for (int j = 0; j < i; j++) {
                free(matrix[j]);
            }
            free(matrix);
            return NULL;
        }
        
        // Initialize with pattern
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = i * 10 + j;
        }
    }
    
    return matrix;
}

void solution6_free_matrix(int **matrix, int rows) {
    if (matrix == NULL) return;
    
    for (int i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);
}

void test_exercise6(void) {
    printf("=== Exercise 6: Dynamic 2D Matrix ===\n\n");
    
    int rows = 3, cols = 4;
    int **matrix = solution6_create_matrix(rows, cols);
    
    if (matrix) {
        printf("Matrix (%d x %d) with pattern i*10 + j:\n", rows, cols);
        for (int i = 0; i < rows; i++) {
            printf("  ");
            for (int j = 0; j < cols; j++) {
                printf("%3d ", matrix[i][j]);
            }
            printf("\n");
        }
        
        solution6_free_matrix(matrix, rows);
        printf("Matrix freed.\n\n");
    }
}


/* ============================================================
 * EXERCISE 7: Dynamic Stack Implementation
 * 
 * Implement a simple stack with:
 * 1. Stack structure (data, top, capacity)
 * 2. create_stack(capacity)
 * 3. push(stack, value) - grows if needed
 * 4. pop(stack) - returns top value
 * 5. destroy_stack(stack)
 * ============================================================ */

typedef struct {
    int *data;
    int top;
    int capacity;
} Stack;

Stack* exercise7_create_stack(int capacity) {
    // YOUR CODE HERE
    return NULL;
}

int exercise7_push(Stack *s, int value) {
    // YOUR CODE HERE
    // Return 1 on success, 0 on failure
    return 0;
}

int exercise7_pop(Stack *s, int *value) {
    // YOUR CODE HERE
    // Return 1 on success, 0 if empty
    return 0;
}

void exercise7_destroy_stack(Stack *s) {
    // YOUR CODE HERE
}

// Solutions
Stack* solution7_create_stack(int capacity) {
    if (capacity <= 0) return NULL;
    
    Stack *s = (Stack *)malloc(sizeof(Stack));
    if (s == NULL) return NULL;
    
    s->data = (int *)malloc(capacity * sizeof(int));
    if (s->data == NULL) {
        free(s);
        return NULL;
    }
    
    s->top = -1;
    s->capacity = capacity;
    return s;
}

int solution7_push(Stack *s, int value) {
    if (s == NULL) return 0;
    
    // Grow if needed
    if (s->top + 1 >= s->capacity) {
        int new_capacity = s->capacity * 2;
        int *new_data = (int *)realloc(s->data, new_capacity * sizeof(int));
        if (new_data == NULL) return 0;
        
        s->data = new_data;
        s->capacity = new_capacity;
    }
    
    s->data[++s->top] = value;
    return 1;
}

int solution7_pop(Stack *s, int *value) {
    if (s == NULL || s->top < 0) return 0;
    
    *value = s->data[s->top--];
    return 1;
}

void solution7_destroy_stack(Stack *s) {
    if (s) {
        free(s->data);
        free(s);
    }
}

void test_exercise7(void) {
    printf("=== Exercise 7: Dynamic Stack ===\n\n");
    
    Stack *s = solution7_create_stack(2);
    if (s == NULL) return;
    
    printf("Created stack with capacity 2\n");
    
    // Push more than capacity to trigger growth
    printf("Pushing: ");
    for (int i = 1; i <= 5; i++) {
        solution7_push(s, i * 10);
        printf("%d ", i * 10);
    }
    printf("\n");
    
    printf("Popping: ");
    int value;
    while (solution7_pop(s, &value)) {
        printf("%d ", value);
    }
    printf("\n");
    
    solution7_destroy_stack(s);
    printf("Stack destroyed.\n\n");
}


/* ============================================================
 * EXERCISE 8: String Array (Array of Strings)
 * 
 * Write functions to:
 * 1. create_string_array(count) - allocate array of string pointers
 * 2. set_string(arr, index, str) - duplicate and store a string
 * 3. free_string_array(arr, count) - free all strings and array
 * ============================================================ */

char** exercise8_create_string_array(int count) {
    // YOUR CODE HERE
    return NULL;
}

int exercise8_set_string(char **arr, int index, const char *str) {
    // YOUR CODE HERE
    // Return 1 on success, 0 on failure
    return 0;
}

void exercise8_free_string_array(char **arr, int count) {
    // YOUR CODE HERE
}

// Solutions
char** solution8_create_string_array(int count) {
    if (count <= 0) return NULL;
    
    char **arr = (char **)calloc(count, sizeof(char *));
    return arr;  // NULL if failed, all pointers initialized to NULL
}

int solution8_set_string(char **arr, int index, const char *str) {
    if (arr == NULL || str == NULL) return 0;
    
    // Free existing string if any
    free(arr[index]);
    
    arr[index] = (char *)malloc(strlen(str) + 1);
    if (arr[index] == NULL) return 0;
    
    strcpy(arr[index], str);
    return 1;
}

void solution8_free_string_array(char **arr, int count) {
    if (arr == NULL) return;
    
    for (int i = 0; i < count; i++) {
        free(arr[i]);
    }
    free(arr);
}

void test_exercise8(void) {
    printf("=== Exercise 8: String Array ===\n\n");
    
    int count = 3;
    char **names = solution8_create_string_array(count);
    
    if (names) {
        solution8_set_string(names, 0, "Alice");
        solution8_set_string(names, 1, "Bob");
        solution8_set_string(names, 2, "Charlie");
        
        printf("String array contents:\n");
        for (int i = 0; i < count; i++) {
            printf("  [%d]: %s\n", i, names[i]);
        }
        
        solution8_free_string_array(names, count);
        printf("String array freed.\n\n");
    }
}


/* ============================================================
 * EXERCISE 9: Simple Linked List
 * 
 * Implement:
 * 1. Node structure (int data, Node *next)
 * 2. list_append(head_ptr, value) - add to end
 * 3. list_print(head) - print all values
 * 4. list_free(head) - free entire list
 * 5. list_length(head) - count nodes
 * ============================================================ */

typedef struct Node9 {
    int data;
    struct Node9 *next;
} Node9;

void exercise9_list_append(Node9 **head, int value) {
    // YOUR CODE HERE
}

void exercise9_list_print(Node9 *head) {
    // YOUR CODE HERE
}

void exercise9_list_free(Node9 *head) {
    // YOUR CODE HERE
}

int exercise9_list_length(Node9 *head) {
    // YOUR CODE HERE
    return 0;
}

// Solutions
void solution9_list_append(Node9 **head, int value) {
    Node9 *new_node = (Node9 *)malloc(sizeof(Node9));
    if (new_node == NULL) return;
    
    new_node->data = value;
    new_node->next = NULL;
    
    if (*head == NULL) {
        *head = new_node;
        return;
    }
    
    Node9 *current = *head;
    while (current->next != NULL) {
        current = current->next;
    }
    current->next = new_node;
}

void solution9_list_print(Node9 *head) {
    Node9 *current = head;
    while (current != NULL) {
        printf("%d", current->data);
        if (current->next) printf(" -> ");
        current = current->next;
    }
    printf(" -> NULL\n");
}

void solution9_list_free(Node9 *head) {
    while (head != NULL) {
        Node9 *temp = head;
        head = head->next;
        free(temp);
    }
}

int solution9_list_length(Node9 *head) {
    int count = 0;
    while (head != NULL) {
        count++;
        head = head->next;
    }
    return count;
}

void test_exercise9(void) {
    printf("=== Exercise 9: Linked List ===\n\n");
    
    Node9 *head = NULL;
    
    solution9_list_append(&head, 10);
    solution9_list_append(&head, 20);
    solution9_list_append(&head, 30);
    solution9_list_append(&head, 40);
    
    printf("List: ");
    solution9_list_print(head);
    printf("Length: %d\n", solution9_list_length(head));
    
    solution9_list_free(head);
    printf("List freed.\n\n");
}


/* ============================================================
 * EXERCISE 10: Memory Pool (Advanced)
 * 
 * Implement a simple memory pool that:
 * 1. Pre-allocates a large block of memory
 * 2. Allocates fixed-size chunks from the pool
 * 3. Tracks which chunks are free
 * 4. Supports reset (mark all as free)
 * ============================================================ */

typedef struct {
    void *memory;       // The memory block
    int *free_list;     // Track free slots (1=free, 0=used)
    size_t chunk_size;  // Size of each chunk
    int count;          // Number of chunks
    int used;           // Number of used chunks
} MemoryPool;

MemoryPool* exercise10_pool_create(size_t chunk_size, int count) {
    // YOUR CODE HERE
    return NULL;
}

void* exercise10_pool_alloc(MemoryPool *pool) {
    // YOUR CODE HERE
    return NULL;
}

void exercise10_pool_free(MemoryPool *pool, void *ptr) {
    // YOUR CODE HERE
}

void exercise10_pool_destroy(MemoryPool *pool) {
    // YOUR CODE HERE
}

// Solutions
MemoryPool* solution10_pool_create(size_t chunk_size, int count) {
    if (chunk_size == 0 || count <= 0) return NULL;
    
    MemoryPool *pool = (MemoryPool *)malloc(sizeof(MemoryPool));
    if (pool == NULL) return NULL;
    
    pool->memory = malloc(chunk_size * count);
    pool->free_list = (int *)malloc(count * sizeof(int));
    
    if (pool->memory == NULL || pool->free_list == NULL) {
        free(pool->memory);
        free(pool->free_list);
        free(pool);
        return NULL;
    }
    
    pool->chunk_size = chunk_size;
    pool->count = count;
    pool->used = 0;
    
    // Mark all as free
    for (int i = 0; i < count; i++) {
        pool->free_list[i] = 1;
    }
    
    return pool;
}

void* solution10_pool_alloc(MemoryPool *pool) {
    if (pool == NULL || pool->used >= pool->count) return NULL;
    
    // Find first free slot
    for (int i = 0; i < pool->count; i++) {
        if (pool->free_list[i]) {
            pool->free_list[i] = 0;
            pool->used++;
            return (char *)pool->memory + (i * pool->chunk_size);
        }
    }
    
    return NULL;
}

void solution10_pool_free(MemoryPool *pool, void *ptr) {
    if (pool == NULL || ptr == NULL) return;
    
    // Calculate which slot this pointer belongs to
    size_t offset = (char *)ptr - (char *)pool->memory;
    int index = offset / pool->chunk_size;
    
    if (index >= 0 && index < pool->count && !pool->free_list[index]) {
        pool->free_list[index] = 1;
        pool->used--;
    }
}

void solution10_pool_destroy(MemoryPool *pool) {
    if (pool) {
        free(pool->memory);
        free(pool->free_list);
        free(pool);
    }
}

void test_exercise10(void) {
    printf("=== Exercise 10: Memory Pool ===\n\n");
    
    // Create a pool for 5 integers
    MemoryPool *pool = solution10_pool_create(sizeof(int), 5);
    if (pool == NULL) return;
    
    printf("Created pool: %d chunks of %zu bytes each\n", 
           pool->count, pool->chunk_size);
    
    // Allocate some chunks
    int *a = (int *)solution10_pool_alloc(pool);
    int *b = (int *)solution10_pool_alloc(pool);
    int *c = (int *)solution10_pool_alloc(pool);
    
    if (a && b && c) {
        *a = 100;
        *b = 200;
        *c = 300;
        
        printf("Allocated 3 chunks: %d, %d, %d\n", *a, *b, *c);
        printf("Pool used: %d/%d\n", pool->used, pool->count);
        
        // Free middle one
        solution10_pool_free(pool, b);
        printf("After freeing 'b': used = %d/%d\n", pool->used, pool->count);
        
        // Allocate again - should reuse freed slot
        int *d = (int *)solution10_pool_alloc(pool);
        if (d) {
            *d = 999;
            printf("Allocated new chunk: %d\n", *d);
        }
    }
    
    solution10_pool_destroy(pool);
    printf("Pool destroyed.\n\n");
}


/* ============================================================
 * ANSWER KEY SUMMARY
 * ============================================================ */

void print_answer_key(void) {
    printf("\n");
    printf("╔═══════════════════════════════════════════════════════════════╗\n");
    printf("║              DYNAMIC MEMORY - ANSWER KEY                      ║\n");
    printf("╚═══════════════════════════════════════════════════════════════╝\n\n");
    
    printf("Exercise 1: Basic malloc/free\n");
    printf("─────────────────────────────────────────\n");
    printf("Key concepts:\n");
    printf("  - malloc(n * sizeof(type)) to allocate array\n");
    printf("  - Always check for NULL\n");
    printf("  - free() before returning\n\n");
    
    printf("Exercise 2: String duplication\n");
    printf("─────────────────────────────────────────\n");
    printf("Key concepts:\n");
    printf("  - strlen() + 1 for null terminator\n");
    printf("  - strcpy() to copy string\n");
    printf("  - Return pointer, caller must free\n\n");
    
    printf("Exercise 3: calloc\n");
    printf("─────────────────────────────────────────\n");
    printf("Key concepts:\n");
    printf("  - calloc(count, size) = malloc + zero-init\n");
    printf("  - Two parameters vs one\n\n");
    
    printf("Exercise 4: realloc\n");
    printf("─────────────────────────────────────────\n");
    printf("Key concepts:\n");
    printf("  - realloc() may return different address\n");
    printf("  - Old data is preserved\n");
    printf("  - New memory is uninitialized\n\n");
    
    printf("Exercise 5: Array concatenation\n");
    printf("─────────────────────────────────────────\n");
    printf("Key concepts:\n");
    printf("  - Allocate total size needed\n");
    printf("  - Copy in sequence\n\n");
    
    printf("Exercise 6: 2D Matrix\n");
    printf("─────────────────────────────────────────\n");
    printf("Key concepts:\n");
    printf("  - Allocate array of pointers first\n");
    printf("  - Then allocate each row\n");
    printf("  - Free in reverse order\n\n");
    
    printf("Exercise 7: Dynamic Stack\n");
    printf("─────────────────────────────────────────\n");
    printf("Key concepts:\n");
    printf("  - Structure holds data ptr + metadata\n");
    printf("  - realloc to grow when full\n");
    printf("  - Track top index and capacity\n\n");
    
    printf("Exercise 8: String Array\n");
    printf("─────────────────────────────────────────\n");
    printf("Key concepts:\n");
    printf("  - Array of char* pointers\n");
    printf("  - Each string allocated separately\n");
    printf("  - Free each string, then array\n\n");
    
    printf("Exercise 9: Linked List\n");
    printf("─────────────────────────────────────────\n");
    printf("Key concepts:\n");
    printf("  - malloc for each node\n");
    printf("  - Double pointer for head modification\n");
    printf("  - Traverse and free each node\n\n");
    
    printf("Exercise 10: Memory Pool\n");
    printf("─────────────────────────────────────────\n");
    printf("Key concepts:\n");
    printf("  - Pre-allocate large block\n");
    printf("  - Track which chunks are used\n");
    printf("  - Faster than repeated malloc\n");
    printf("  - Good for fixed-size allocations\n\n");
    
    printf("═══════════════════════════════════════════════════════════════\n");
    printf("MEMORY MANAGEMENT GOLDEN RULES:\n");
    printf("═══════════════════════════════════════════════════════════════\n");
    printf("1. Every malloc/calloc MUST have a corresponding free\n");
    printf("2. ALWAYS check if allocation returned NULL\n");
    printf("3. Set pointers to NULL after freeing\n");
    printf("4. Never use memory after freeing (dangling pointer)\n");
    printf("5. Never free the same memory twice (double free)\n");
    printf("6. Free nested allocations in reverse order\n");
    printf("7. Use valgrind to detect memory leaks\n");
    printf("═══════════════════════════════════════════════════════════════\n");
}


/* ============================================================
 * MAIN FUNCTION
 * ============================================================ */

int main() {
    printf("╔═══════════════════════════════════════════════════════════════╗\n");
    printf("║       DYNAMIC MEMORY ALLOCATION - EXERCISES                   ║\n");
    printf("╚═══════════════════════════════════════════════════════════════╝\n\n");
    
    test_exercise1();
    test_exercise2();
    test_exercise3();
    test_exercise4();
    test_exercise5();
    test_exercise6();
    test_exercise7();
    test_exercise8();
    test_exercise9();
    test_exercise10();
    
    print_answer_key();
    
    return 0;
}
Exercises - C Programming Tutorial | DeepML