Dynamic Memory Management
Dynamic memory is like renting an apartment — you apply when you need it (malloc), and you move out when you're done (free). If you never move out, you keep hogging resources — that's a memory leak.
Why Dynamic Memory
Local variables on the stack disappear when a function returns, and array sizes must be determined at compile time. When the data size is only known at runtime, or you need data to persist across function calls, you need dynamic memory on the heap.
int n;
scanf("%d", &n);
int arr[n];
malloc.
malloc and free
malloc allocates a specified number of bytes on the heap and returns void *. You must free the memory when done.
int *p = (int *)malloc(sizeof(int) * 5);
if (p == NULL) {
printf("Memory allocation failed\n");
return 1;
}
for (int i = 0; i < 5; i++) {
p[i] = i * 10;
}
for (int i = 0; i < 5; i++) {
printf("%d ", p[i]);
}
free(p);
p = NULL;
0 10 20 30 40
malloc does not initialize memory — the contents are garbage values. Always check the return value for NULL after allocation. Setting the pointer to NULL after free is good practice to prevent dangling pointers.
calloc
calloc allocates memory and initializes every bit to 0. Its parameters are the number of elements and the size of each element.
int *p = (int *)calloc(5, sizeof(int));
if (p) {
for (int i = 0; i < 5; i++) {
printf("%d ", p[i]);
}
free(p);
}
0 0 0 0 0
malloc vs calloc:
| Function | Initialization | Parameters |
|---|---|---|
malloc |
No initialization | Total bytes |
calloc |
Zeroed | Element count, size of each element |
realloc
realloc resizes previously allocated memory — it can expand or shrink.
int *p = (int *)malloc(sizeof(int) * 3);
p[0] = 10; p[1] = 20; p[2] = 30;
int *tmp = (int *)realloc(p, sizeof(int) * 5);
if (tmp) {
p = tmp;
p[3] = 40;
p[4] = 50;
for (int i = 0; i < 5; i++) {
printf("%d ", p[i]);
}
free(p);
}
10 20 30 40 50
realloc may return a new address (original data is automatically copied) or the same address. Never write p = realloc(p, ...) — if it fails and returns NULL, the original pointer is lost. Use a temporary variable to receive the result.
Dynamic 1D Array
Example
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n;
printf("Enter number of elements: ");
scanf("%d", &n);
int *arr = (int *)calloc(n, sizeof(int));
if (!arr) return 1;
for (int i = 0; i < n; i++) {
arr[i] = (i + 1) * (i + 1);
}
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
arr = NULL;
return 0;
}
Enter number of elements: 5
1 4 9 16 25
Dynamic 2D Array
There are two common approaches to implementing dynamic 2D arrays.
Approach 1: Pointer Array (Each Row Allocated Independently)
int rows = 3, cols = 4;
int matrix = (int )malloc(sizeof(int *) * rows);
for (int i = 0; i < rows; i++) {
matrix[i] = (int *)calloc(cols, sizeof(int));
}
matrix[1][2] = 99;
printf("%d\n", matrix[1][2]);
for (int i = 0; i < rows; i++) {
free(matrix[i]);
}
free(matrix);
99
Approach 2: Contiguous Memory (Single Allocation)
int rows = 3, cols = 4;
int *buf = (int *)calloc(rows * cols, sizeof(int));
int matrix = (int )malloc(sizeof(int *) * rows);
for (int i = 0; i < rows; i++) {
matrix[i] = buf + i * cols;
}
matrix[2][3] = 77;
printf("%d\n", matrix[2][3]);
free(matrix);
free(buf);
77
Example
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int rows = 3, cols = 4;
int m = (int )malloc(sizeof(int *) * rows);
int *buf = (int *)calloc(rows * cols, sizeof(int));
for (int i = 0; i < rows; i++) {
m[i] = buf + i * cols;
}
int val = 1;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
m[i][j] = val++;
}
}
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%3d", m[i][j]);
}
printf("\n");
}
free(m);
free(buf);
return 0;
}
1 2 3 4
5 6 7 8
9 10 11 12
Common Dynamic Memory Errors
Forgetting to free — Memory Leak
void leak(void) {
int *p = (int *)malloc(sizeof(int) * 100);
}
When the function ends, the local variable p is gone, but the 100 ints of heap memory are still occupied and can never be freed.
Double free
int *p = (int *)malloc(sizeof(int));
free(p);
free(p);
free on the same memory twice is undefined behavior and may crash the program. Set the pointer to NULL immediately after free — calling free(NULL) is safe.
Using Freed Memory
int *p = (int *)malloc(sizeof(int));
*p = 42;
free(p);
printf("%d\n", *p);
Out-of-Bounds Access
int *p = (int *)malloc(sizeof(int) * 5);
p[5] = 100;
5 elements were allocated with indices 0-4, so p[5] is out of bounds.
valgrind Introduction
Valgrind is a memory-checking tool on Linux that can detect memory leaks, out-of-bounds access, and uninitialized reads.
gcc -g -o myapp myapp.c
valgrind --leak-check=full ./myapp
Typical output:
==12345== HEAP SUMMARY:
==12345== in use at exit: 400 bytes in 1 blocks
==12345== total heap usage: 2 allocs, 1 frees, 800 bytes allocated
==12345==
==12345== 400 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12345== by 0x10915E: leak (myapp.c:5)
❓ FAQ
📖 Summary
malloc(size)allocates without initializing,calloc(n, size)allocates and zeroes,realloc(ptr, size)resizes- For dynamic 2D arrays, the "single contiguous allocation + pointer array row mapping" approach is recommended
- malloc and free must always be paired — forgetting free causes memory leaks
- Common errors: leaks, double free, dangling pointers, out-of-bounds access
- valgrind can automatically detect memory issues — use it regularly during development
📝 Exercises
- Write a program: the user inputs n, dynamically allocate n ints, read n numbers, sort and output them, then free the memory
- Write dynamic 2D array functions:
int create_matrix(int rows, int cols)andvoid free_matrix(int m, int rows). In main, create a 4x5 matrix, assign values, and print - Intentionally create a memory leak in your code, then use valgrind (or Dr. Memory) to detect it and examine the output



