Until now, the size of the array was determined at compile time. But what if you need an array with the size of N received as input at runtime? It's time for Dynamic Memory Allocation.
Learning Objectives
- Understand the difference between stack and heap.
- Know the roles of
malloc,calloc,realloc, andfree. - Avoid memory leaks, double free, and dangling pointers.
- Create a dynamic 1-dimensional/2-dimensional array.
Why do you need dynamic memory? — Stack and Heap
memory area
┌─────────────────────┐ ◄── High address
│ Stack ▼ │ Local variables, function call information
│ │ Automatic Allocate/Deallocate, fast, small (typically 1 to 8 MB)
├─────────────────────┤
│ ↕ (empty space) │
├─────────────────────┤
│ Heap ▲ │ with-malloc/free Direct management
│ │ Almost no size limitations
├─────────────────────┤
│ data/code area │
└─────────────────────┘ ◄── Low address
With just the stack:
- Size must be known at compile time
- Size limitations (large arrays will overflow the stack)
- Automatically disappears when the function ends (cannot be taken out of the function)
Since the heap does not have the above restrictions, patterns such as N inputs and N slots, Function creates and returns are possible.
Key concepts
1) 4 functions (<stdlib.h>)
| function | Meaning |
|---|---|
void *malloc(size_t n) |
allocate n bytes (initialize X) |
void *calloc(size_t cnt, size_t sz) |
After allocating cnt*sz bytes, initialized to 0 |
void *realloc(void *p, size_t n) |
Reallocate existing block to n bytes |
void free(void *p) |
off |
2) Basic usage pattern — “Allocate, Check, Enable, Free”
int *arr = malloc(n * sizeof(int)); //1) Assignment
if (arr == NULL) { /*Failure handling*/ } // 2) Check
for (int i = 0; i < n; i++) arr[i] = i; //3) Use
free(arr); //4) Release
arr = NULL; //(Optional) Anti-dangling
1 call to malloc = 1 call to free. Always a pair.
3) Safe use of realloc
int *tmp = realloc(arr, new_size * sizeof(int));
if (tmp == NULL) {
/*Failure: arr is still valid, can be used as is*/
} else {
arr = tmp;
}
You must receive the result as a temporary variable so that you do not lose the original in case of failure.
4) Dynamic two-dimensional array
int **mat = malloc(rows * sizeof(int *));
for (int i = 0; i < rows; i++)
mat[i] = malloc(cols * sizeof(int));
Disable in reverse order:
for (int i = 0; i < rows; i++) free(mat[i]);
free(mat);
See as example
Example 1 — ex01_malloc.c: Dynamic array
int *arr = malloc((size_t)n * sizeof(int));
if (arr == NULL) { perror("malloc"); return 1; }
Input: 5
Enter array size: 10 20 30 40 50
Bottom line: The input N determines the size of the array. Not possible with a static array.
Example 2 — ex02_calloc.c:0 initialization
Execution result
Immediately after calloc: 0 0 0 0 0
Key point: malloc is a garbage value, calloc is initialized to 0. 0 If initialization is required, calloc.
Example 3 — ex03_realloc.c: Dynamic expansion
Add 10 units and double the capacity when full.
Execution result
(Capacity expansion: 8)
(Capacity expansion: 16)
Final arrangement: 100 200 300 400 500 600 700 800 900 1000
Core: Core patterns of dynamic data structures (variable-length vectors, queues, etc.).
Example 4 — ex04_2d.c: Dynamic two-dimensional array
Execution result
1 2 3 4
5 6 7 8
9 10 11 12
Bottom line: int ** is a "pointer's pointer". Allocate twice, free twice.
See it from a different perspective — “Borrowing books from the library”
malloc = borrow a book from the library (go and borrow it when you need it)
free = Return the borrowed book (return it when you finish using it)
If you borrow it and don’t return it → memory leak
Return the same book twice → double free
Return the borrowed book and read it again → Dangling pointer (use-after-free)
Libraries (OS) don't have infinite books, so if leaks build up, you'll eventually run out of books to borrow.
Common Mistakes
- Leak: Forgetting to call
freeor missing it from the branch. - Double free: free the same pointer twice → undefined behavior.
- Use after release: Dangling pointer.
- Size calculation mistake: Omitting
* sizeof(int)likemalloc(n). mallocresult NULL not checked: If memory is insufficient, NULL is returned → If written as is, it will be a segfault.- Receiving the
reallocresult with the same pointer: In case of failure, the original is lost.
Cleanup
- The stack is automatic, fast, and small, and the heap is manual, flexible, and large.
- Assigned to
malloc/calloc/realloc, released tofree. - Allocation and deallocation are always paired 1:1.
- Receive the result of
reallocas a temporary variable to prepare for failure. - Leakage, double release, and dangling are the three major memory accidents.
Try it yourself
cd src
gcc -std=c11 -Wall -o ex01 ex01_malloc.c && echo 5 | ./ex01
gcc -std=c11 -Wall -o ex02 ex02_calloc.c && ./ex02
gcc -std=c11 -Wall -o ex03 ex03_realloc.c && ./ex03
gcc -std=c11 -Wall -o ex04 ex04_2d.c && ./ex04
Application:
- Check if there are any leaks with
valgrind ./ex01(Linux). - Try changing it to receive input at
ex04's row/column execution time.
Finish
You have completed this track.