C Memory Management and Dynamic Allocation
45 minDynamic memory allocation enables programs to request memory at runtime, providing flexibility for data structures whose size isn't known at compile time. C provides malloc(), calloc(), realloc(), and free() for dynamic memory management. Understanding dynamic allocation is essential for building flexible C programs. However, manual memory management requires careful attention to prevent leaks and errors.
malloc() allocates uninitialized memory of a specified size in bytes. It returns a void pointer that should be cast to the appropriate type. malloc() returns NULL on failure, so error checking is essential. Allocated memory contains garbage values and must be initialized before use. Understanding malloc() enables basic dynamic allocation.
calloc() allocates zero-initialized memory for an array of elements. calloc() takes element count and element size, making it convenient for arrays. calloc() also returns NULL on failure. Zero-initialization makes calloc() safer than malloc() for arrays. Understanding calloc() enables safe array allocation.
realloc() resizes existing memory allocations, enabling dynamic arrays that grow or shrink. realloc() may move data to a new location, so pointers to the old location become invalid. realloc() returns NULL on failure, but the original memory remains allocated. Understanding realloc() enables dynamic data structures.
free() deallocates memory allocated by malloc(), calloc(), or realloc(). Every allocation must be freed exactly once. Double-free errors (freeing twice) and use-after-free errors (using freed memory) cause undefined behavior. Memory leaks occur when allocated memory isn't freed. Understanding free() enables proper memory management.
Memory management best practices include checking allocation success, initializing allocated memory, freeing all allocations, avoiding use-after-free, and using tools like Valgrind to detect leaks. Memory pools and custom allocators can optimize performance for specific use cases. Understanding best practices prevents common memory errors.
Key Concepts
- Dynamic memory allocation uses malloc(), calloc(), realloc(), and free().
- All allocations must be freed to prevent memory leaks.
- Memory allocation can fail, requiring error checking.
- realloc() may move memory, invalidating old pointers.
- Proper memory management prevents leaks and undefined behavior.
Learning Objectives
Master
- Allocating and freeing dynamic memory
- Using malloc(), calloc(), and realloc()
- Implementing dynamic data structures
- Avoiding memory leaks and errors
Develop
- Understanding memory management in C
- Designing efficient memory allocation strategies
- Implementing safe memory management practices
Tips
- Check malloc() return: if (ptr == NULL) handle error.
- Free all allocations: free(ptr); after use.
- Set pointer to NULL after free: ptr = NULL; prevents dangling pointers.
- Use calloc() for arrays: calloc(count, size) for zero-initialized memory.
Common Pitfalls
- Not freeing allocated memory, causing memory leaks.
- Using freed memory, causing undefined behavior.
- Double-freeing memory, causing crashes.
- Not checking allocation success, causing crashes on allocation failure.
Summary
- Dynamic memory allocation enables flexible runtime memory management.
- All allocations must be freed to prevent leaks.
- Error checking is essential for reliable memory allocation.
- Understanding memory management prevents common errors.
Exercise
Create a program demonstrating dynamic memory allocation and management.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Memory pool structure
typedef struct {
void *pool;
size_t size;
size_t used;
size_t block_size;
} MemoryPool;
// Create a memory pool
MemoryPool* createMemoryPool(size_t total_size, size_t block_size) {
MemoryPool *pool = malloc(sizeof(MemoryPool));
if (!pool) return NULL;
pool->pool = malloc(total_size);
if (!pool->pool) {
free(pool);
return NULL;
}
pool->size = total_size;
pool->used = 0;
pool->block_size = block_size;
return pool;
}
// Allocate from pool
void* poolAlloc(MemoryPool *pool) {
if (pool->used + pool->block_size > pool->size) {
return NULL; // Pool is full
}
void *ptr = (char*)pool->pool + pool->used;
pool->used += pool->block_size;
return ptr;
}
// Free the entire pool
void freeMemoryPool(MemoryPool *pool) {
if (pool) {
free(pool->pool);
free(pool);
}
}
// Dynamic array structure
typedef struct {
int *data;
size_t size;
size_t capacity;
} DynamicArray;
// Initialize dynamic array
DynamicArray* createDynamicArray(size_t initial_capacity) {
DynamicArray *arr = malloc(sizeof(DynamicArray));
if (!arr) return NULL;
arr->data = malloc(initial_capacity * sizeof(int));
if (!arr->data) {
free(arr);
return NULL;
}
arr->size = 0;
arr->capacity = initial_capacity;
return arr;
}
// Add element to dynamic array
int addElement(DynamicArray *arr, int value) {
if (arr->size >= arr->capacity) {
size_t new_capacity = arr->capacity * 2;
int *new_data = realloc(arr->data, new_capacity * sizeof(int));
if (!new_data) return 0;
arr->data = new_data;
arr->capacity = new_capacity;
}
arr->data[arr->size++] = value;
return 1;
}
// Remove element from dynamic array
int removeElement(DynamicArray *arr, size_t index) {
if (index >= arr->size) return 0;
// Shift elements
for (size_t i = index; i < arr->size - 1; i++) {
arr->data[i] = arr->data[i + 1];
}
arr->size--;
// Shrink if too much unused space
if (arr->size < arr->capacity / 4 && arr->capacity > 4) {
size_t new_capacity = arr->capacity / 2;
int *new_data = realloc(arr->data, new_capacity * sizeof(int));
if (new_data) {
arr->data = new_data;
arr->capacity = new_capacity;
}
}
return 1;
}
// Free dynamic array
void freeDynamicArray(DynamicArray *arr) {
if (arr) {
free(arr->data);
free(arr);
}
}
// Memory leak detection (simple version)
typedef struct {
void *ptr;
char *file;
int line;
size_t size;
} MemoryTracker;
#define MAX_TRACKED 1000
static MemoryTracker tracked_ptrs[MAX_TRACKED];
static int tracked_count = 0;
void* trackMalloc(size_t size, const char *file, int line) {
void *ptr = malloc(size);
if (ptr && tracked_count < MAX_TRACKED) {
tracked_ptrs[tracked_count].ptr = ptr;
tracked_ptrs[tracked_count].file = (char*)file;
tracked_ptrs[tracked_count].line = line;
tracked_ptrs[tracked_count].size = size;
tracked_count++;
}
return ptr;
}
void trackFree(void *ptr) {
for (int i = 0; i < tracked_count; i++) {
if (tracked_ptrs[i].ptr == ptr) {
// Remove from tracking
for (int j = i; j < tracked_count - 1; j++) {
tracked_ptrs[j] = tracked_ptrs[j + 1];
}
tracked_count--;
break;
}
}
free(ptr);
}
void printMemoryLeaks() {
if (tracked_count == 0) {
printf("No memory leaks detected!
");
return;
}
printf("Memory leaks detected (%d allocations):
", tracked_count);
for (int i = 0; i < tracked_count; i++) {
printf(" Leak %d: %p (%zu bytes) at %s:%d
",
i + 1, tracked_ptrs[i].ptr, tracked_ptrs[i].size,
tracked_ptrs[i].file, tracked_ptrs[i].line);
}
}
// Redefine malloc and free for tracking
#define malloc(size) trackMalloc(size, __FILE__, __LINE__)
#define free(ptr) trackFree(ptr)
int main() {
printf("=== Dynamic Memory Allocation Demo ===
");
// Test memory pool
printf("1. Memory Pool Test:
");
MemoryPool *pool = createMemoryPool(1024, 64);
if (pool) {
void *ptr1 = poolAlloc(pool);
void *ptr2 = poolAlloc(pool);
printf("Allocated 2 blocks from pool
");
printf("Pool usage: %zu/%zu bytes
", pool->used, pool->size);
freeMemoryPool(pool);
printf("Memory pool freed
");
}
// Test dynamic array
printf("2. Dynamic Array Test:
");
DynamicArray *arr = createDynamicArray(2);
if (arr) {
for (int i = 0; i < 10; i++) {
addElement(arr, i * 10);
}
printf("Array size: %zu, capacity: %zu
", arr->size, arr->capacity);
printf("Elements: ");
for (size_t i = 0; i < arr->size; i++) {
printf("%d ", arr->data[i]);
}
printf("
");
removeElement(arr, 5);
printf("After removing element at index 5: ");
for (size_t i = 0; i < arr->size; i++) {
printf("%d ", arr->data[i]);
}
printf("
");
freeDynamicArray(arr);
printf("Dynamic array freed
");
}
// Test memory tracking
printf("3. Memory Tracking Test:
");
int *ptr1 = malloc(100);
int *ptr2 = malloc(200);
char *ptr3 = malloc(50);
printf("Allocated 3 blocks
");
printMemoryLeaks();
free(ptr1);
printf("\nFreed ptr1
");
printMemoryLeaks();
free(ptr2);
free(ptr3);
printf("\nFreed all remaining blocks
");
printMemoryLeaks();
return 0;
}
Exercise Tips
- Always check malloc() return: if (ptr == NULL) handle error.
- Free all allocations: match every malloc() with free().
- Use realloc() carefully: it may move memory, invalidating old pointers.
- Use memory debugging tools: Valgrind to detect leaks and errors.