Docs
README
Dynamic Memory Allocation
Table of Contents
- ā¢Introduction
- ā¢Stack vs Heap Memory
- ā¢malloc Function
- ā¢calloc Function
- ā¢realloc Function
- ā¢free Function
- ā¢Memory Allocation Best Practices
- ā¢Common Memory Errors
- ā¢Dynamic Arrays
- ā¢Dynamic 2D Arrays
- ā¢Dynamic Structures
- ā¢Memory Debugging Tools
- ā¢Summary
Introduction
Dynamic Memory Allocation allows programs to request memory at runtime from the heap. Unlike stack memory (fixed at compile time), heap memory can be:
- ā¢Allocated when needed
- ā¢Resized during execution
- ā¢Freed when no longer required
Why Use Dynamic Memory?
| Use Case | Reason |
|---|---|
| Unknown size at compile time | User input determines array size |
| Large data structures | Stack has limited size |
| Data persistence | Survives function scope |
| Flexible data structures | Linked lists, trees, graphs |
| Efficient memory use | Allocate only what's needed |
Memory Allocation Functions
| Function | Purpose | Header |
|---|---|---|
malloc() | Allocate uninitialized memory | <stdlib.h> |
calloc() | Allocate zero-initialized memory | <stdlib.h> |
realloc() | Resize previously allocated memory | <stdlib.h> |
free() | Release allocated memory | <stdlib.h> |
Stack vs Heap Memory
Stack Memory
void function() {
int x = 10; // Stack: automatic allocation
int arr[100]; // Stack: fixed size array
} // Automatically freed when function returns
Characteristics:
- ā¢Automatic allocation/deallocation
- ā¢Fast allocation (just move stack pointer)
- ā¢Limited size (typically 1-8 MB)
- ā¢LIFO (Last In, First Out)
- ā¢Scope-limited lifetime
Heap Memory
void function() {
int *p = malloc(sizeof(int)); // Heap: manual allocation
int *arr = malloc(100 * sizeof(int));
// Must be freed manually!
free(p);
free(arr);
}
Characteristics:
- ā¢Manual allocation/deallocation
- ā¢Slower than stack
- ā¢Much larger (limited by RAM)
- ā¢No automatic cleanup
- ā¢Persists until freed
Memory Layout Diagram
High Address
āāāāāāāāāāāāāāāāāāāāāāāā
ā Command Line ā
ā Arguments ā
āāāāāāāāāāāāāāāāāāāāāāāā¤
ā Stack ā ā Local variables, function calls
ā ā grows down ā
ā ā
ā ā
ā ā grows up ā
ā Heap ā ā Dynamic memory (malloc, etc.)
āāāāāāāāāāāāāāāāāāāāāāāā¤
ā BSS ā ā Uninitialized global/static
āāāāāāāāāāāāāāāāāāāāāāāā¤
ā Data ā ā Initialized global/static
āāāāāāāāāāāāāāāāāāāāāāāā¤
ā Text (Code) ā ā Program instructions
āāāāāāāāāāāāāāāāāāāāāāāā
Low Address
malloc Function
Syntax
void* malloc(size_t size);
- ā¢size: Number of bytes to allocate
- ā¢Returns: Pointer to allocated memory, or
NULLif failed
Basic Usage
#include <stdio.h>
#include <stdlib.h>
int main() {
// Allocate memory for one integer
int *ptr = (int*)malloc(sizeof(int));
if (ptr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
*ptr = 42;
printf("Value: %d\n", *ptr);
free(ptr); // Don't forget!
return 0;
}
Allocating Arrays
// Allocate array of 10 integers
int *arr = (int*)malloc(10 * sizeof(int));
if (arr != NULL) {
for (int i = 0; i < 10; i++) {
arr[i] = i * 10;
}
// Use array...
free(arr);
}
Important Notes
- ā¢Memory is uninitialized - contains garbage values
- ā¢Always check for NULL - allocation can fail
- ā¢Always use sizeof() - portable across platforms
- ā¢Cast is optional in C - but explicit casts improve readability
// With cast (explicit)
int *p = (int*)malloc(sizeof(int));
// Without cast (valid C, not C++)
int *p = malloc(sizeof(int));
calloc Function
Syntax
void* calloc(size_t num, size_t size);
- ā¢num: Number of elements
- ā¢size: Size of each element
- ā¢Returns: Pointer to zero-initialized memory, or
NULL
Comparison with malloc
// Using malloc - memory contains garbage
int *arr1 = (int*)malloc(5 * sizeof(int));
// arr1[0] to arr1[4] contain random values!
// Using calloc - memory is zeroed
int *arr2 = (int*)calloc(5, sizeof(int));
// arr2[0] to arr2[4] are all 0
When to Use calloc
| Use calloc when... | Use malloc when... |
|---|---|
| Need zero-initialized memory | Will initialize immediately |
| Allocating arrays | Single element allocation |
| Security-sensitive data | Performance is critical |
Example
#include <stdio.h>
#include <stdlib.h>
int main() {
int n = 5;
// Allocate and zero-initialize array
int *arr = (int*)calloc(n, sizeof(int));
if (arr == NULL) {
return 1;
}
// All elements are 0
printf("Initial values: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]); // 0 0 0 0 0
}
printf("\n");
free(arr);
return 0;
}
realloc Function
Syntax
void* realloc(void *ptr, size_t new_size);
- ā¢ptr: Pointer to previously allocated memory
- ā¢new_size: New size in bytes
- ā¢Returns: Pointer to resized memory (may be different!), or
NULL
How realloc Works
- ā¢If space available after current block: extends in place
- ā¢Otherwise: allocates new block, copies data, frees old block
- ā¢If new_size is 0: equivalent to free()
- ā¢If ptr is NULL: equivalent to malloc()
Important Pattern
// WRONG - may lose original data on failure
ptr = realloc(ptr, new_size); // If returns NULL, original is lost!
// CORRECT - preserve original on failure
int *temp = realloc(ptr, new_size);
if (temp != NULL) {
ptr = temp; // Update only on success
} else {
// Handle error, original ptr still valid
}
Growing an Array
#include <stdio.h>
#include <stdlib.h>
int main() {
int capacity = 2;
int size = 0;
int *arr = (int*)malloc(capacity * sizeof(int));
if (arr == NULL) return 1;
// Add elements, grow as needed
int values[] = {10, 20, 30, 40, 50};
for (int i = 0; i < 5; i++) {
if (size >= capacity) {
// Double the capacity
capacity *= 2;
int *temp = (int*)realloc(arr, capacity * sizeof(int));
if (temp == NULL) {
free(arr);
return 1;
}
arr = temp;
printf("Grew to capacity %d\n", capacity);
}
arr[size++] = values[i];
}
printf("Final array: ");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
Shrinking Memory
// Shrink array from 100 to 50 elements
int *arr = (int*)malloc(100 * sizeof(int));
// ... use 100 elements ...
// Now we only need 50
int *temp = (int*)realloc(arr, 50 * sizeof(int));
if (temp != NULL) {
arr = temp; // Shrank successfully
}
free Function
Syntax
void free(void *ptr);
Rules for free()
- ā¢
Only free dynamically allocated memory
int x = 10; free(&x); // WRONG! x is on stack - ā¢
Don't free twice (double free)
int *p = malloc(sizeof(int)); free(p); free(p); // WRONG! Undefined behavior - ā¢
Set pointer to NULL after freeing
int *p = malloc(sizeof(int)); free(p); p = NULL; // Good practice - ā¢
free(NULL) is safe
int *p = NULL; free(p); // OK, does nothing
Memory Leak Prevention
// Memory leak - allocated but never freed
void leak() {
int *p = malloc(100 * sizeof(int));
// Function returns without free()
}
// Correct
void noLeak() {
int *p = malloc(100 * sizeof(int));
// ... use p ...
free(p);
}
Memory Allocation Best Practices
1. Always Check Return Value
int *arr = (int*)malloc(n * sizeof(int));
if (arr == NULL) {
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}
2. Use sizeof with Variable, Not Type
// Good - automatically correct if type changes
int *arr = malloc(n * sizeof(*arr));
// Less safe - must update if type changes
int *arr = malloc(n * sizeof(int));
3. Free Memory in Reverse Order of Allocation
// Allocate
char *name = malloc(50);
int *data = malloc(100 * sizeof(int));
// Free in reverse order
free(data);
free(name);
4. Use Wrapper Functions
void* safe_malloc(size_t size) {
void *ptr = malloc(size);
if (ptr == NULL && size != 0) {
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}
return ptr;
}
5. Initialize Pointers
int *ptr = NULL; // Good: initialize to NULL
// Later, can safely check
if (ptr == NULL) {
ptr = malloc(sizeof(int));
}
6. Document Ownership
/**
* Creates a new string buffer.
* @param size Buffer size
* @return Newly allocated buffer. Caller must free().
*/
char* createBuffer(size_t size);
Common Memory Errors
1. Memory Leak
void leak() {
int *p = malloc(1000 * sizeof(int));
// Forgot to free!
} // Memory is lost
Fix: Always pair malloc with free.
2. Dangling Pointer
int *p = malloc(sizeof(int));
*p = 42;
free(p);
printf("%d\n", *p); // WRONG! p is dangling
Fix: Set pointer to NULL after free.
3. Double Free
int *p = malloc(sizeof(int));
free(p);
free(p); // CRASH or corruption!
Fix: Set to NULL after free; check before free.
4. Buffer Overflow
int *arr = malloc(5 * sizeof(int));
for (int i = 0; i <= 5; i++) { // Off by one!
arr[i] = i; // arr[5] is out of bounds
}
Fix: Use correct bounds checking.
5. Use After Free
int *p = malloc(sizeof(int));
*p = 10;
free(p);
*p = 20; // WRONG! Memory already freed
Fix: Don't access freed memory.
6. Wrong Size
int *arr = malloc(10); // WRONG! Only 10 bytes, not 10 ints
int *arr = malloc(10 * sizeof(int)); // CORRECT
7. Freeing Stack Memory
int arr[10];
free(arr); // CRASH! arr is on stack
Fix: Only free heap memory.
Dynamic Arrays
Creating Dynamic Arrays
int n = 10;
int *arr = (int*)malloc(n * sizeof(int));
// Use like normal array
for (int i = 0; i < n; i++) {
arr[i] = i * 2;
}
free(arr);
Resizable Array (Vector)
typedef struct {
int *data;
int size;
int capacity;
} Vector;
Vector* vector_create() {
Vector *v = (Vector*)malloc(sizeof(Vector));
v->data = (int*)malloc(4 * sizeof(int));
v->size = 0;
v->capacity = 4;
return v;
}
void vector_push(Vector *v, int value) {
if (v->size >= v->capacity) {
v->capacity *= 2;
v->data = (int*)realloc(v->data, v->capacity * sizeof(int));
}
v->data[v->size++] = value;
}
void vector_free(Vector *v) {
free(v->data);
free(v);
}
Dynamic 2D Arrays
Method 1: Array of Pointers
int rows = 3, cols = 4;
// Allocate row pointers
int **matrix = (int**)malloc(rows * sizeof(int*));
// Allocate each row
for (int i = 0; i < rows; i++) {
matrix[i] = (int*)malloc(cols * sizeof(int));
}
// Use
matrix[1][2] = 42;
// Free (in reverse order)
for (int i = 0; i < rows; i++) {
free(matrix[i]);
}
free(matrix);
Method 2: Contiguous Allocation
int rows = 3, cols = 4;
// Allocate all data contiguously
int *data = (int*)malloc(rows * cols * sizeof(int));
int **matrix = (int**)malloc(rows * sizeof(int*));
// Set up row pointers
for (int i = 0; i < rows; i++) {
matrix[i] = data + (i * cols);
}
// Use same way
matrix[1][2] = 42;
// Simpler free
free(data);
free(matrix);
Comparison
| Aspect | Array of Pointers | Contiguous |
|---|---|---|
| Allocation calls | rows + 1 | 2 |
| Memory overhead | More pointers | Less |
| Cache locality | Poor | Excellent |
| Ragged arrays | Possible | No |
| Free calls | rows + 1 | 2 |
Dynamic Structures
Allocating Structures
typedef struct {
char name[50];
int age;
} Person;
// Single structure
Person *p = (Person*)malloc(sizeof(Person));
strcpy(p->name, "Alice");
p->age = 30;
// Array of structures
Person *people = (Person*)malloc(10 * sizeof(Person));
strcpy(people[0].name, "Bob");
people[0].age = 25;
free(p);
free(people);
Structures with Dynamic Members
typedef struct {
char *name; // Dynamically allocated
int *scores; // Dynamically allocated array
int num_scores;
} Student;
Student* create_student(const char *name, int num_scores) {
Student *s = (Student*)malloc(sizeof(Student));
s->name = (char*)malloc(strlen(name) + 1);
strcpy(s->name, name);
s->scores = (int*)calloc(num_scores, sizeof(int));
s->num_scores = num_scores;
return s;
}
void free_student(Student *s) {
free(s->name); // Free name first
free(s->scores); // Free scores
free(s); // Free structure last
}
Linked List Node
typedef struct Node {
int data;
struct Node *next;
} Node;
Node* create_node(int data) {
Node *n = (Node*)malloc(sizeof(Node));
n->data = data;
n->next = NULL;
return n;
}
void free_list(Node *head) {
while (head != NULL) {
Node *next = head->next;
free(head);
head = next;
}
}
Memory Debugging Tools
Valgrind (Linux)
# Compile with debug symbols
gcc -g -o program program.c
# Run with Valgrind
valgrind --leak-check=full ./program
Output:
==12345== HEAP SUMMARY:
==12345== in use at exit: 0 bytes in 0 blocks
==12345== total heap usage: 5 allocs, 5 frees, 1,024 bytes allocated
==12345==
==12345== All heap blocks were freed -- no leaks are possible
AddressSanitizer (GCC/Clang)
# Compile with sanitizer
gcc -fsanitize=address -g -o program program.c
# Run - will detect memory errors
./program
Simple Tracking Wrapper
#include <stdio.h>
#include <stdlib.h>
static int alloc_count = 0;
void* tracked_malloc(size_t size) {
void *ptr = malloc(size);
if (ptr) {
alloc_count++;
printf("ALLOC: %zu bytes at %p (total: %d)\n",
size, ptr, alloc_count);
}
return ptr;
}
void tracked_free(void *ptr) {
if (ptr) {
alloc_count--;
printf("FREE: %p (remaining: %d)\n", ptr, alloc_count);
free(ptr);
}
}
Summary
Function Quick Reference
| Function | Purpose | Returns | Notes |
|---|---|---|---|
malloc(size) | Allocate bytes | void* or NULL | Uninitialized |
calloc(n, size) | Allocate n elements | void* or NULL | Zero-initialized |
realloc(ptr, size) | Resize allocation | void* or NULL | Preserves data |
free(ptr) | Release memory | void | Safe with NULL |
Memory Management Checklist
- ⢠Always check malloc/calloc/realloc return value
- ⢠Use sizeof() with the variable, not the type
- ⢠Free memory when done
- ⢠Set pointer to NULL after freeing
- ⢠Never double-free
- ⢠Never access freed memory
- ⢠Free in reverse order of allocation
- ⢠Handle realloc failure without losing original pointer
Common Patterns
// Safe allocation
int *arr = malloc(n * sizeof(*arr));
if (arr == NULL) {
// Handle error
}
// Safe realloc
int *temp = realloc(arr, new_size * sizeof(*arr));
if (temp == NULL) {
// Handle error, arr still valid
} else {
arr = temp;
}
// Safe free
free(arr);
arr = NULL;
Practice Exercises
- ā¢Implement a dynamic string that can grow automatically
- ā¢Create a matrix multiplication function with dynamic allocation
- ā¢Build a simple memory pool allocator
- ā¢Implement a dynamic stack data structure
- ā¢Create a program that reads unknown number of inputs from user
See the exercises.c file for hands-on practice problems with solutions.