c
exercises
exercises.c🔧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;
}