Introduction to Dynamic Memory Allocation
Dynamic memory allocation allows programs to request memory at runtime, providing flexibility to handle data structures whose size isn't known at compile time. Unlike static memory allocation (stack), dynamic memory is allocated on the heap and must be manually managed.
System Architecture Overview
Dynamic Memory Architecture ├── Memory Segments │ ├── Stack (Local variables, function calls) │ ├── Heap (Dynamic allocations) │ ├── Data (Global/Static variables) │ └── Text (Program code) ├── Allocation Functions │ ├── malloc() - Allocate uninitialized memory │ ├── calloc() - Allocate and initialize to zero │ ├── realloc() - Resize existing allocation │ └── free() - Release allocated memory └── Memory Management ├── Allocation Tracking ├── Memory Leak Prevention ├── Dangling Pointer Prevention └── Fragmentation Handling
Core Memory Allocation Functions
1. malloc() - Memory Allocation
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr;
int n = 5;
// Allocate memory for 5 integers
ptr = (int*)malloc(n * sizeof(int));
// Check if allocation succeeded
if (ptr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
printf("Memory allocated successfully\n");
// Use the allocated memory
for (int i = 0; i < n; i++) {
ptr[i] = i * 10;
printf("ptr[%d] = %d\n", i, ptr[i]);
}
// Free the allocated memory
free(ptr);
return 0;
}
Output:
Memory allocated successfully ptr[0] = 0 ptr[1] = 10 ptr[2] = 20 ptr[3] = 30 ptr[4] = 40
2. calloc() - Contiguous Allocation
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr;
int n = 5;
// Allocate and zero-initialize memory for 5 integers
ptr = (int*)calloc(n, sizeof(int));
if (ptr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
// calloc initializes all bytes to zero
printf("calloc initializes to zero:\n");
for (int i = 0; i < n; i++) {
printf("ptr[%d] = %d\n", i, ptr[i]);
}
free(ptr);
return 0;
}
Output:
calloc initializes to zero: ptr[0] = 0 ptr[1] = 0 ptr[2] = 0 ptr[3] = 0 ptr[4] = 0
3. realloc() - Reallocation
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr;
int n = 5;
// Initial allocation
ptr = (int*)malloc(n * sizeof(int));
if (ptr == NULL) {
printf("Initial allocation failed!\n");
return 1;
}
// Initialize data
for (int i = 0; i < n; i++) {
ptr[i] = i + 1;
}
printf("Original array (%d elements):\n", n);
for (int i = 0; i < n; i++) {
printf("%d ", ptr[i]);
}
printf("\n");
// Resize to 10 elements
int new_size = 10;
int *new_ptr = (int*)realloc(ptr, new_size * sizeof(int));
if (new_ptr == NULL) {
printf("Reallocation failed!\n");
free(ptr);
return 1;
}
ptr = new_ptr;
// Initialize new elements
for (int i = n; i < new_size; i++) {
ptr[i] = i + 1;
}
printf("Resized array (%d elements):\n", new_size);
for (int i = 0; i < new_size; i++) {
printf("%d ", ptr[i]);
}
printf("\n");
free(ptr);
return 0;
}
Output:
Original array (5 elements): 1 2 3 4 5 Resized array (10 elements): 1 2 3 4 5 6 7 8 9 10
4. free() - Memory Deallocation
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr1, *ptr2, *ptr3;
// Multiple allocations
ptr1 = (int*)malloc(100 * sizeof(int));
ptr2 = (int*)calloc(50, sizeof(int));
ptr3 = (int*)malloc(200 * sizeof(int));
if (ptr1 && ptr2 && ptr3) {
printf("All allocations successful\n");
// Use memory...
// Free in reverse order (good practice)
free(ptr3);
free(ptr2);
free(ptr1);
printf("All memory freed\n");
} else {
// Clean up any successful allocations if some failed
if (ptr1) free(ptr1);
if (ptr2) free(ptr2);
if (ptr3) free(ptr3);
printf("Some allocations failed\n");
}
return 0;
}
Common Dynamic Data Structures
1. Dynamic Array
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int *data;
int size;
int capacity;
} DynamicArray;
// Initialize dynamic array
DynamicArray* createArray(int initialCapacity) {
DynamicArray *arr = (DynamicArray*)malloc(sizeof(DynamicArray));
if (arr == NULL) return NULL;
arr->data = (int*)malloc(initialCapacity * sizeof(int));
if (arr->data == NULL) {
free(arr);
return NULL;
}
arr->size = 0;
arr->capacity = initialCapacity;
return arr;
}
// Add element to array
void append(DynamicArray *arr, int value) {
if (arr->size >= arr->capacity) {
// Double the capacity
arr->capacity *= 2;
arr->data = (int*)realloc(arr->data, arr->capacity * sizeof(int));
if (arr->data == NULL) {
printf("Failed to resize array!\n");
return;
}
printf("Array resized to capacity: %d\n", arr->capacity);
}
arr->data[arr->size++] = value;
}
// Get element at index
int get(DynamicArray *arr, int index) {
if (index < 0 || index >= arr->size) {
printf("Index out of bounds!\n");
return -1;
}
return arr->data[index];
}
// Free array
void freeArray(DynamicArray *arr) {
if (arr) {
free(arr->data);
free(arr);
}
}
// Print array
void printArray(DynamicArray *arr) {
printf("Array (size=%d, capacity=%d): ", arr->size, arr->capacity);
for (int i = 0; i < arr->size; i++) {
printf("%d ", arr->data[i]);
}
printf("\n");
}
int main() {
DynamicArray *arr = createArray(3);
printf("Appending elements:\n");
for (int i = 1; i <= 10; i++) {
append(arr, i * 10);
printf("Appended %d\n", i * 10);
}
printArray(arr);
printf("\nElement at index 5: %d\n", get(arr, 5));
freeArray(arr);
return 0;
}
2. Singly Linked List
#include <stdio.h>
#include <stdlib.h>
// Node structure
typedef struct Node {
int data;
struct Node *next;
} Node;
// List structure
typedef struct {
Node *head;
Node *tail;
int size;
} LinkedList;
// Create new node
Node* createNode(int data) {
Node *newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) return NULL;
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// Initialize list
LinkedList* createList() {
LinkedList *list = (LinkedList*)malloc(sizeof(LinkedList));
if (list == NULL) return NULL;
list->head = NULL;
list->tail = NULL;
list->size = 0;
return list;
}
// Insert at beginning
void insertAtBeginning(LinkedList *list, int data) {
Node *newNode = createNode(data);
if (newNode == NULL) return;
newNode->next = list->head;
list->head = newNode;
if (list->tail == NULL) {
list->tail = newNode;
}
list->size++;
}
// Insert at end
void insertAtEnd(LinkedList *list, int data) {
Node *newNode = createNode(data);
if (newNode == NULL) return;
if (list->tail == NULL) {
list->head = list->tail = newNode;
} else {
list->tail->next = newNode;
list->tail = newNode;
}
list->size++;
}
// Delete first node
int deleteFromBeginning(LinkedList *list) {
if (list->head == NULL) return -1;
Node *temp = list->head;
int data = temp->data;
list->head = list->head->next;
if (list->head == NULL) {
list->tail = NULL;
}
free(temp);
list->size--;
return data;
}
// Search for element
Node* search(LinkedList *list, int key) {
Node *current = list->head;
while (current != NULL) {
if (current->data == key) {
return current;
}
current = current->next;
}
return NULL;
}
// Print list
void printList(LinkedList *list) {
Node *current = list->head;
printf("List (size=%d): ", list->size);
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
// Free entire list
void freeList(LinkedList *list) {
Node *current = list->head;
Node *next;
while (current != NULL) {
next = current->next;
free(current);
current = next;
}
free(list);
}
int main() {
LinkedList *list = createList();
printf("Inserting at beginning:\n");
insertAtBeginning(list, 30);
insertAtBeginning(list, 20);
insertAtBeginning(list, 10);
printList(list);
printf("\nInserting at end:\n");
insertAtEnd(list, 40);
insertAtEnd(list, 50);
printList(list);
printf("\nSearching for 30:\n");
Node *found = search(list, 30);
if (found) {
printf("Found: %d\n", found->data);
} else {
printf("Not found\n");
}
printf("\nDeleting from beginning:\n");
int deleted = deleteFromBeginning(list);
printf("Deleted: %d\n", deleted);
printList(list);
freeList(list);
return 0;
}
3. Dynamic 2D Array
#include <stdio.h>
#include <stdlib.h>
// Function to create a 2D array
int** create2DArray(int rows, int cols) {
// Allocate memory for row pointers
int **arr = (int**)malloc(rows * sizeof(int*));
if (arr == NULL) return NULL;
// Allocate memory for each row
for (int i = 0; i < rows; i++) {
arr[i] = (int*)malloc(cols * sizeof(int));
if (arr[i] == NULL) {
// Clean up previously allocated rows
for (int j = 0; j < i; j++) {
free(arr[j]);
}
free(arr);
return NULL;
}
}
return arr;
}
// Function to initialize 2D array
void initialize2DArray(int **arr, int rows, int cols) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
arr[i][j] = i * cols + j + 1;
}
}
}
// Function to print 2D array
void print2DArray(int **arr, int rows, int cols) {
printf("2D Array (%d x %d):\n", rows, cols);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%4d ", arr[i][j]);
}
printf("\n");
}
}
// Function to free 2D array
void free2DArray(int **arr, int rows) {
for (int i = 0; i < rows; i++) {
free(arr[i]);
}
free(arr);
}
int main() {
int rows = 3, cols = 4;
int **matrix = create2DArray(rows, cols);
if (matrix == NULL) {
printf("Failed to allocate 2D array!\n");
return 1;
}
initialize2DArray(matrix, rows, cols);
print2DArray(matrix, rows, cols);
free2DArray(matrix, rows);
return 0;
}
4. Dynamic String Array
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char **strings;
int size;
int capacity;
} StringArray;
// Initialize string array
StringArray* createStringArray(int initialCapacity) {
StringArray *arr = (StringArray*)malloc(sizeof(StringArray));
if (arr == NULL) return NULL;
arr->strings = (char**)malloc(initialCapacity * sizeof(char*));
if (arr->strings == NULL) {
free(arr);
return NULL;
}
arr->size = 0;
arr->capacity = initialCapacity;
return arr;
}
// Add string to array
void addString(StringArray *arr, const char *str) {
if (arr->size >= arr->capacity) {
arr->capacity *= 2;
arr->strings = (char**)realloc(arr->strings, arr->capacity * sizeof(char*));
if (arr->strings == NULL) {
printf("Failed to resize string array!\n");
return;
}
}
arr->strings[arr->size] = (char*)malloc(strlen(str) + 1);
if (arr->strings[arr->size] != NULL) {
strcpy(arr->strings[arr->size], str);
arr->size++;
}
}
// Remove string at index
void removeString(StringArray *arr, int index) {
if (index < 0 || index >= arr->size) return;
free(arr->strings[index]);
// Shift remaining strings
for (int i = index; i < arr->size - 1; i++) {
arr->strings[i] = arr->strings[i + 1];
}
arr->size--;
}
// Print all strings
void printStringArray(StringArray *arr) {
printf("String Array (size=%d):\n", arr->size);
for (int i = 0; i < arr->size; i++) {
printf(" [%d]: %s\n", i, arr->strings[i]);
}
}
// Free string array
void freeStringArray(StringArray *arr) {
for (int i = 0; i < arr->size; i++) {
free(arr->strings[i]);
}
free(arr->strings);
free(arr);
}
int main() {
StringArray *arr = createStringArray(3);
addString(arr, "Hello");
addString(arr, "World");
addString(arr, "Dynamic");
addString(arr, "Memory");
addString(arr, "Allocation");
printStringArray(arr);
printf("\nRemoving index 2...\n");
removeString(arr, 2);
printStringArray(arr);
freeStringArray(arr);
return 0;
}
Advanced Memory Management Techniques
1. Memory Pool
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
typedef struct {
void *pool;
size_t blockSize;
size_t totalBlocks;
size_t usedBlocks;
uint8_t *bitmap;
} MemoryPool;
// Create memory pool
MemoryPool* createPool(size_t blockSize, size_t totalBlocks) {
MemoryPool *pool = (MemoryPool*)malloc(sizeof(MemoryPool));
if (pool == NULL) return NULL;
pool->pool = malloc(blockSize * totalBlocks);
if (pool->pool == NULL) {
free(pool);
return NULL;
}
pool->bitmap = (uint8_t*)calloc((totalBlocks + 7) / 8, sizeof(uint8_t));
if (pool->bitmap == NULL) {
free(pool->pool);
free(pool);
return NULL;
}
pool->blockSize = blockSize;
pool->totalBlocks = totalBlocks;
pool->usedBlocks = 0;
return pool;
}
// Allocate from pool
void* poolAlloc(MemoryPool *pool) {
if (pool->usedBlocks >= pool->totalBlocks) {
return NULL; // Pool full
}
// Find free block
for (size_t i = 0; i < pool->totalBlocks; i++) {
size_t byteIndex = i / 8;
size_t bitIndex = i % 8;
if (!(pool->bitmap[byteIndex] & (1 << bitIndex))) {
// Mark as used
pool->bitmap[byteIndex] |= (1 << bitIndex);
pool->usedBlocks++;
return (char*)pool->pool + (i * pool->blockSize);
}
}
return NULL;
}
// Free from pool
void poolFree(MemoryPool *pool, void *ptr) {
if (ptr == NULL) return;
// Calculate block index
ptrdiff_t offset = (char*)ptr - (char*)pool->pool;
if (offset < 0 || offset >= (ptrdiff_t)(pool->blockSize * pool->totalBlocks)) {
return; // Pointer not from this pool
}
size_t index = offset / pool->blockSize;
// Mark as free
size_t byteIndex = index / 8;
size_t bitIndex = index % 8;
pool->bitmap[byteIndex] &= ~(1 << bitIndex);
pool->usedBlocks--;
}
// Destroy pool
void destroyPool(MemoryPool *pool) {
if (pool) {
free(pool->bitmap);
free(pool->pool);
free(pool);
}
}
int main() {
// Create pool of 10 blocks, each 32 bytes
MemoryPool *pool = createPool(32, 10);
if (pool == NULL) {
printf("Failed to create pool\n");
return 1;
}
printf("Pool created: %zu blocks of %zu bytes\n",
pool->totalBlocks, pool->blockSize);
// Allocate some blocks
void *ptr1 = poolAlloc(pool);
void *ptr2 = poolAlloc(pool);
void *ptr3 = poolAlloc(pool);
printf("Allocated 3 blocks\n");
printf("Used blocks: %zu\n", pool->usedBlocks);
// Free a block
poolFree(pool, ptr2);
printf("Freed 1 block\n");
printf("Used blocks: %zu\n", pool->usedBlocks);
// Allocate another
void *ptr4 = poolAlloc(pool);
printf("Allocated another block\n");
printf("Used blocks: %zu\n", pool->usedBlocks);
destroyPool(pool);
return 0;
}
2. Reference Counting
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int refCount;
char data[];
} RefCounted;
// Create reference counted object
RefCounted* createRefCounted(const char *str) {
size_t len = strlen(str) + 1;
RefCounted *obj = (RefCounted*)malloc(sizeof(RefCounted) + len);
if (obj) {
obj->refCount = 1;
strcpy(obj->data, str);
}
return obj;
}
// Increment reference count
void retain(RefCounted *obj) {
if (obj) {
obj->refCount++;
}
}
// Decrement reference count and free if zero
void release(RefCounted *obj) {
if (obj && --obj->refCount == 0) {
printf("Freeing object: %s\n", obj->data);
free(obj);
}
}
int main() {
RefCounted *obj1 = createRefCounted("Hello, World!");
printf("obj1 refCount: %d\n", obj1->refCount);
printf("obj1 data: %s\n", obj1->data);
// Create another reference
RefCounted *obj2 = obj1;
retain(obj2);
printf("After retain, obj1 refCount: %d\n", obj1->refCount);
// Release one reference
release(obj1);
printf("After first release, obj2 refCount: %d\n", obj2->refCount);
// Release final reference
release(obj2);
return 0;
}
3. Memory Leak Detection
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef DEBUG_MEMORY
typedef struct Allocation {
void *ptr;
size_t size;
const char *file;
int line;
struct Allocation *next;
} Allocation;
static Allocation *allocations = NULL;
static size_t totalAllocated = 0;
static size_t totalFreed = 0;
// Track allocation
void* trackMalloc(size_t size, const char *file, int line) {
void *ptr = malloc(size);
if (ptr) {
Allocation *alloc = malloc(sizeof(Allocation));
if (alloc) {
alloc->ptr = ptr;
alloc->size = size;
alloc->file = file;
alloc->line = line;
alloc->next = allocations;
allocations = alloc;
totalAllocated += size;
}
}
return ptr;
}
// Track free
void trackFree(void *ptr) {
if (ptr) {
Allocation **prev = &allocations;
Allocation *curr = allocations;
while (curr) {
if (curr->ptr == ptr) {
*prev = curr->next;
totalFreed += curr->size;
free(curr);
break;
}
prev = &curr->next;
curr = curr->next;
}
free(ptr);
}
}
// Report memory leaks
void reportMemoryLeaks() {
if (allocations == NULL) {
printf("No memory leaks detected!\n");
printf("Total allocated: %zu bytes\n", totalAllocated);
printf("Total freed: %zu bytes\n", totalFreed);
return;
}
printf("\n=== MEMORY LEAK REPORT ===\n");
printf("Leaked allocations:\n");
Allocation *curr = allocations;
while (curr) {
printf(" %zu bytes at %p (allocated at %s:%d)\n",
curr->size, curr->ptr, curr->file, curr->line);
curr = curr->next;
}
printf("Total leaked: %zu bytes\n", totalAllocated - totalFreed);
printf("========================\n\n");
}
#define malloc(size) trackMalloc(size, __FILE__, __LINE__)
#define free(ptr) trackFree(ptr)
#endif
int main() {
// Test with leak detection
int *arr1 = (int*)malloc(10 * sizeof(int));
int *arr2 = (int*)malloc(20 * sizeof(int));
char *str = (char*)malloc(100 * sizeof(char));
// Use memory
for (int i = 0; i < 10; i++) {
arr1[i] = i;
}
// Free only some allocations
free(arr1);
free(arr2); // Comment this out to create a leak
// str is intentionally leaked for demonstration
#ifdef DEBUG_MEMORY
reportMemoryLeaks();
#endif
return 0;
}
Common Pitfalls and Best Practices
1. Memory Leaks
#include <stdio.h>
#include <stdlib.h>
// BAD - Memory leak
void badFunction() {
int *ptr = (int*)malloc(100 * sizeof(int));
// Use ptr but forget to free
// Memory leak!
}
// GOOD - Proper cleanup
void goodFunction() {
int *ptr = (int*)malloc(100 * sizeof(int));
if (ptr) {
// Use ptr
// ...
free(ptr); // Don't forget!
}
}
// BAD - Losing pointer
void losingPointer() {
int *ptr = (int*)malloc(100 * sizeof(int));
ptr = NULL; // Lost reference - can't free now!
}
int main() {
goodFunction();
return 0;
}
2. Dangling Pointers
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = (int*)malloc(sizeof(int));
*ptr = 42;
printf("Before free: %d\n", *ptr);
free(ptr);
// BAD - Using after free (dangling pointer)
// printf("After free: %d\n", *ptr); // Undefined behavior!
// GOOD - Set to NULL after freeing
ptr = NULL;
// This will crash cleanly instead of undefined behavior
// if (ptr) printf("%d\n", *ptr);
return 0;
}
3. Double Free
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = (int*)malloc(sizeof(int));
free(ptr);
// BAD - Double free
// free(ptr); // Undefined behavior, may crash
// GOOD - Set to NULL after free
ptr = NULL;
free(ptr); // free(NULL) is safe
return 0;
}
4. Buffer Overflow
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
int *arr = (int*)malloc(5 * sizeof(int));
if (arr == NULL) return 1;
// BAD - Writing beyond allocated memory
for (int i = 0; i <= 5; i++) { // Should be i < 5
arr[i] = i; // Buffer overflow when i = 5
}
// GOOD - Proper bounds checking
for (int i = 0; i < 5; i++) {
arr[i] = i;
}
free(arr);
// String buffer overflow example
char *str = (char*)malloc(10 * sizeof(char));
if (str) {
// BAD - Copying too much data
// strcpy(str, "This string is way too long!");
// GOOD - Using strncpy
strncpy(str, "Short", 9);
str[9] = '\0';
printf("String: %s\n", str);
free(str);
}
return 0;
}
5. Allocation Failure Handling
#include <stdio.h>
#include <stdlib.h>
void safeMemoryOperations() {
int *ptr = (int*)malloc(100 * sizeof(int));
// ALWAYS check allocation success
if (ptr == NULL) {
printf("Memory allocation failed!\n");
return;
}
// Use memory...
free(ptr);
}
// Function that allocates multiple blocks
int* allocateMultiple() {
int *ptr1 = (int*)malloc(100 * sizeof(int));
if (ptr1 == NULL) return NULL;
int *ptr2 = (int*)malloc(200 * sizeof(int));
if (ptr2 == NULL) {
free(ptr1); // Clean up first allocation
return NULL;
}
int *ptr3 = (int*)malloc(300 * sizeof(int));
if (ptr3 == NULL) {
free(ptr1);
free(ptr2);
return NULL;
}
// Success - use all three
// In real code, you'd return something meaningful
free(ptr1);
free(ptr2);
free(ptr3);
return ptr1; // Simplified
}
int main() {
safeMemoryOperations();
allocateMultiple();
return 0;
}
Memory Alignment Considerations
#include <stdio.h>
#include <stdlib.h>
#include <stdalign.h>
typedef struct {
char c;
int i;
double d;
} Misaligned;
typedef struct {
double d;
int i;
char c;
} Aligned;
int main() {
printf("=== Alignment Examples ===\n\n");
printf("Misaligned structure:\n");
printf(" Size: %zu bytes\n", sizeof(Misaligned));
printf(" Offset of c: %zu\n", offsetof(Misaligned, c));
printf(" Offset of i: %zu\n", offsetof(Misaligned, i));
printf(" Offset of d: %zu\n\n", offsetof(Misaligned, d));
printf("Aligned structure:\n");
printf(" Size: %zu bytes\n", sizeof(Aligned));
printf(" Offset of d: %zu\n", offsetof(Aligned, d));
printf(" Offset of i: %zu\n", offsetof(Aligned, i));
printf(" Offset of c: %zu\n\n", offsetof(Aligned, c));
// Allocate aligned memory
void *ptr;
int err = posix_memalign(&ptr, 64, 1024); // 64-byte alignment
if (err == 0) {
printf("Allocated 64-byte aligned memory at %p\n", ptr);
free(ptr);
}
return 0;
}
Performance Considerations
1. Allocation Overhead
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ITERATIONS 100000
int main() {
clock_t start, end;
double cpu_time_used;
// Measure single large allocation
start = clock();
int *large = (int*)malloc(ITERATIONS * sizeof(int));
if (large) {
for (int i = 0; i < ITERATIONS; i++) {
large[i] = i;
}
free(large);
}
end = clock();
cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("Single large allocation: %.4f seconds\n", cpu_time_used);
// Measure many small allocations
start = clock();
for (int i = 0; i < ITERATIONS; i++) {
int *small = (int*)malloc(sizeof(int));
if (small) {
*small = i;
free(small);
}
}
end = clock();
cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("Many small allocations: %.4f seconds\n", cpu_time_used);
return 0;
}
2. Realloc Efficiency
#include <stdio.h>
#include <stdlib.h>
// Inefficient - reallocating one element at a time
void inefficientGrowth() {
int *arr = NULL;
int size = 0;
for (int i = 0; i < 10000; i++) {
arr = (int*)realloc(arr, (size + 1) * sizeof(int));
if (arr) {
arr[size++] = i;
}
}
free(arr);
}
// Efficient - doubling capacity
void efficientGrowth() {
int *arr = NULL;
int size = 0;
int capacity = 0;
for (int i = 0; i < 10000; i++) {
if (size >= capacity) {
capacity = capacity == 0 ? 1 : capacity * 2;
arr = (int*)realloc(arr, capacity * sizeof(int));
}
if (arr) {
arr[size++] = i;
}
}
free(arr);
}
int main() {
clock_t start, end;
start = clock();
inefficientGrowth();
end = clock();
printf("Inefficient growth: %ld ticks\n", end - start);
start = clock();
efficientGrowth();
end = clock();
printf("Efficient growth: %ld ticks\n", end - start);
return 0;
}
Debugging Memory Issues
1. Using Valgrind (External Tool)
// Compile with: gcc -g program.c -o program
// Run with: valgrind --leak-check=full ./program
#include <stdlib.h>
int main() {
int *leak = (int*)malloc(100 * sizeof(int));
// Forgot to free - Valgrind will report this
int *valid = (int*)malloc(50 * sizeof(int));
free(valid); // Properly freed
return 0;
}
2. Manual Debugging Aids
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Debugging macros
#define DEBUG_ALLOC
#define DEBUG_PRINT(fmt, ...) \
printf("[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#ifdef DEBUG_ALLOC
void* debugMalloc(size_t size, const char *file, int line) {
void *ptr = malloc(size);
DEBUG_PRINT("malloc(%zu) = %p at %s:%d", size, ptr, file, line);
return ptr;
}
void debugFree(void *ptr, const char *file, int line) {
DEBUG_PRINT("free(%p) at %s:%d", ptr, file, line);
free(ptr);
}
#define malloc(size) debugMalloc(size, __FILE__, __LINE__)
#define free(ptr) debugFree(ptr, __FILE__, __LINE__)
#endif
int main() {
int *arr = (int*)malloc(5 * sizeof(int));
for (int i = 0; i < 5; i++) {
arr[i] = i;
}
free(arr);
return 0;
}
Best Practices Summary
Do's and Don'ts
// DO: Check allocation success
int *ptr = (int*)malloc(size * sizeof(int));
if (ptr == NULL) {
// Handle error
}
// DON'T: Assume allocation succeeds
int *ptr = (int*)malloc(size * sizeof(int));
// Use ptr without checking
// DO: Free memory when done
free(ptr);
// DON'T: Forget to free
// memory leak
// DO: Set pointer to NULL after freeing
ptr = NULL;
// DON'T: Use after free
// printf("%d\n", *ptr); // Undefined behavior
// DO: Use sizeof with type
ptr = (int*)malloc(n * sizeof(int));
// DON'T: Hardcode sizes
ptr = (int*)malloc(n * 4); // Assumes int is 4 bytes
// DO: Use calloc for zero-initialized arrays
ptr = (int*)calloc(n, sizeof(int));
// DON'T: Use malloc and memset separately
ptr = (int*)malloc(n * sizeof(int));
memset(ptr, 0, n * sizeof(int)); // Less efficient
// DO: Free in reverse order of allocation
free(ptr3);
free(ptr2);
free(ptr1);
// DON'T: Free in arbitrary order
// Can cause fragmentation
Conclusion
Key Takeaways
- malloc() - Allocates uninitialized memory
- calloc() - Allocates and zero-initializes memory
- realloc() - Resizes existing allocations
- free() - Releases allocated memory
- Always check allocation success
- Always free allocated memory
- Avoid memory leaks, dangling pointers, double frees
- Use appropriate data structures for your needs
Memory Management Rules
- Every malloc/calloc/realloc must have a matching free
- Free memory in the reverse order of allocation
- Set pointers to NULL after freeing
- Check for NULL after allocation
- Don't access memory after freeing it
- Don't free the same memory twice
- Use sizeof for type sizes
- Consider memory pools for frequent allocations
When to Use Dynamic Allocation
- Data structures whose size is unknown at compile time
- Large data that doesn't fit on the stack
- Data that must persist beyond function scope
- Flexible arrays that need to grow/shrink
- Shared data between different parts of program
Dynamic memory allocation is a powerful feature of C that enables flexible and efficient memory usage, but it requires careful management to avoid errors and leaks.