Chapter 2 — Dynamic Memory
`malloc`, `calloc`, `realloc`, `free` — runtime allocation on the **heap**. The boundary between intermediate and "C programmer".
What you'll learn
- 1Allocate, use, and `free` heap memory.
- 2Choose between `malloc`, `calloc`, and `realloc`.
- 3Detect leaks and double frees mentally before they happen.
- 4Build a dynamic array.
Core Concepts
1) `malloc` + `free`
int *p = malloc(10 * sizeof(int));
if (p == NULL) { /* allocation failed */ }
for (int i = 0; i < 10; i++) p[i] = i;
free(p);
p = NULL; // optional but defensive`malloc` does **not** initialize; values are garbage.
2) `calloc`
int *p = calloc(10, sizeof(int)); // zero-initialized`calloc(n, size)` is roughly `malloc(n * size)` + `memset` to 0.
3) `realloc`
p = realloc(p, 20 * sizeof(int)); // grow to 20May return a different pointer; **never** ignore the return value:
int *tmp = realloc(p, 20 * sizeof(int));
if (!tmp) { free(p); /* handle */ }
p = tmp;4) Stack vs heap
| Stack | Heap | |
|---|---|---|
| Allocated by | compiler | `malloc`/`calloc` |
| Freed by | scope exit | manual `free` |
| Size | small, fast | bigger, slower |
| Lifetime | until function returns | until you `free` |
5) Memory bugs to dread
| Bug | Cause |
|---|---|
| Leak | `malloc` without `free` |
| Double free | `free` called twice |
| Use-after-free | dereferencing after `free` |
| Off-by-one | writing one past the end |
| Uninitialized read | using `malloc`'d memory before writing |
Tool: `valgrind ./program` finds most of these.
Examples
Example 1 — `ex01_malloc.c`: basic alloc + use + free
int *p = malloc(5 * sizeof(int));
for (int i = 0; i < 5; i++) p[i] = i * 10;
for (int i = 0; i < 5; i++) printf("%d ", p[i]);
free(p);**Output**
0 10 20 30 40Key: `malloc` gives raw bytes; you fill them yourself.
Example 2 — `ex02_calloc.c`: zero-initialized
int *p = calloc(5, sizeof(int));
for (int i = 0; i < 5; i++) printf("%d ", p[i]);
free(p);**Output**
0 0 0 0 0Key: use `calloc` when zero is the right starting value (most of the time).
Example 3 — `ex03_realloc.c`: grow a dynamic array
int *p = malloc(3 * sizeof(int));
p[0] = 1; p[1] = 2; p[2] = 3;
int *tmp = realloc(p, 5 * sizeof(int));
if (!tmp) { free(p); return 1; }
p = tmp;
p[3] = 4; p[4] = 5;
for (int i = 0; i < 5; i++) printf("%d ", p[i]);
free(p);**Output**
1 2 3 4 5Key: keep the result of `realloc` in a temp pointer to handle failure cleanly.
Example 4 — `ex04_2d.c`: dynamic 2D array
int rows = 3, cols = 4;
int **m = malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) m[i] = malloc(cols * sizeof(int));
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++) m[i][j] = i * cols + j;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) printf("%2d ", m[i][j]);
printf("\n");
}
for (int i = 0; i < rows; i++) free(m[i]);
free(m);**Output**
0 1 2 3
4 5 6 7
8 9 10 11Key: free in the reverse order of allocation.
Common mistakes
- **Not checking `malloc` return** — NULL deref crashes.
- **Forgetting `free`** — leaks add up.
- **`free` then deref** — undefined behavior.
- **Double free** — corrupts the allocator's bookkeeping.
- **Mismatched alloc/free** — `free` only what `malloc/calloc/realloc` returned.
Recap
- `malloc` if you'll fill it yourself; `calloc` for zero-init; `realloc` to grow.
- Every alloc must have exactly one matching `free`.
- Run under `valgrind` while learning.
Try it
cd src
gcc -std=c11 -Wall -o ex01 ex01_malloc.c && ./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💻 Examples
Compilable, runnable examples — see the output yourself.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n;
printf("Array size: ");
scanf("%d", &n);
int *arr = malloc((size_t)n * sizeof(int));
if (arr == NULL) { perror("malloc"); return 1; }
for (int i = 0; i < n; i++) arr[i] = (i + 1) * 10;
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
putchar('\n');
free(arr);
return 0;
}
0 10 20 30 40#include <stdio.h>
#include <stdlib.h>
int main(void) {
size_t n = 5;
int *arr = calloc(n, sizeof(int)); /* 0 */
if (!arr) { perror("calloc"); return 1; }
printf("calloc : ");
for (size_t i = 0; i < n; i++) printf("%d ", arr[i]);
putchar('\n');
free(arr);
return 0;
}
0 0 0 0 0#include <stdio.h>
#include <stdlib.h>
int main(void) {
size_t cap = 4;
size_t len = 0;
int *arr = malloc(cap * sizeof(int));
if (!arr) { perror("malloc"); return 1; }
/* 10 */
for (int i = 0; i < 10; i++) {
if (len == cap) {
cap *= 2;
int *tmp = realloc(arr, cap * sizeof(int));
if (!tmp) { free(arr); perror("realloc"); return 1; }
arr = tmp;
printf("(capacity grew: %zu)\n", cap);
}
arr[len++] = (i + 1) * 100;
}
printf("final array: ");
for (size_t i = 0; i < len; i++) printf("%d ", arr[i]);
putchar('\n');
free(arr);
return 0;
}
1 2 3 4 5#include <stdio.h>
#include <stdlib.h>
int main(void) {
int rows = 3, cols = 4;
/* */
int **mat = malloc((size_t)rows * sizeof(int *));
if (!mat) { perror("malloc"); return 1; }
/* */
for (int i = 0; i < rows; i++) {
mat[i] = malloc((size_t)cols * sizeof(int));
if (!mat[i]) { perror("malloc"); return 1; }
for (int j = 0; j < cols; j++) mat[i][j] = i * cols + j + 1;
}
/* */
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) printf("%4d", mat[i][j]);
putchar('\n');
}
/* */
for (int i = 0; i < rows; i++) free(mat[i]);
free(mat);
return 0;
}
0 1 2 3
4 5 6 7
8 9 10 11📝 Exercises
Try them yourself first, then open the solution to compare.
Problem 1 (hw01.c)
Goal: Allocate an `int[N]` with `malloc`, fill with 1..N, sum it, free.
- Filename: hw01.c
▶Toggle solution
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n;
printf("Enter N: ");
scanf("%d", &n);
if (n <= 0) { printf("N must be positive.\n"); return 1; }
int *arr = malloc((size_t)n * sizeof(int));
if (!arr) { perror("malloc"); return 1; }
printf("Enter %d ints: ", n);
for (int i = 0; i < n; i++) scanf("%d", &arr[i]);
int mx = arr[0], mn = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] > mx) mx = arr[i];
if (arr[i] < mn) mn = arr[i];
}
printf("max: %d\nmin: %d\n", mx, mn);
free(arr);
return 0;
}
Problem 2 (hw02.c)
Goal: Write `char *strdup_mine(const char *s)` that allocates and copies a string.
- Filename: hw02.c
▶Toggle solution
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n, m;
printf("Rows N, cols M: ");
scanf("%d %d", &n, &m);
int **mat = calloc((size_t)n, sizeof(int *));
if (!mat) { perror("calloc"); return 1; }
for (int i = 0; i < n; i++) {
mat[i] = calloc((size_t)m, sizeof(int));
if (!mat[i]) { perror("calloc"); return 1; }
}
int len = (n < m) ? n : m;
for (int i = 0; i < len; i++) mat[i][i] = 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) printf("%2d", mat[i][j]);
putchar('\n');
}
for (int i = 0; i < n; i++) free(mat[i]);
free(mat);
return 0;
}
Problem 3 (hw03.c)
Goal: Read a matrix (rows × cols) into a dynamically allocated 2D array, then print its transpose.
- Filename: hw03.c
▶Toggle solution
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *my_strdup(const char *s) {
size_t len = strlen(s);
char *copy = malloc(len + 1);
if (!copy) return NULL;
memcpy(copy, s, len + 1); /* */
return copy;
}
int main(void) {
char buf[256];
printf("Sentence: ");
if (fgets(buf, sizeof(buf), stdin) == NULL) return 1;
buf[strcspn(buf, "\n")] = '\0';
char *dup = my_strdup(buf);
if (!dup) { perror("malloc"); return 1; }
printf("original: %s\n", buf);
printf("copy: %s\n", dup);
free(dup);
return 0;
}
All lecture materials and example code are openly available on GitHub.
View on GitHub ↗