Last 30 Days
No notifications
So far our arrays have had fixed sizes known at compile time. But what if you don't know the size until the program runs (e.g., user enters n)?
Use dynamic memory from the heap — a large pool you can request bytes from with malloc and return with free.
#include <stdlib.h>int n;
scanf("%d", &n);
int *arr = malloc(n * sizeof(int)); // ask for n ints
if (arr == NULL) { /* out of memory */ }
for (int i = 0; i < n; i++) arr[i] = i * i;
free(arr); // give it back when done
arr = NULL; // good habit — avoid dangling pointer
| Function | Purpose |
malloc(size) | Allocate size bytes; contents are garbage |
calloc(n, size) | Allocate n × size bytes; zero-filled |
realloc(p, new_size) | Resize an existing block |
free(p) | Release a previously allocated block |
T *p = malloc(N * sizeof(T));
if (!p) { /* handle failure */ }/* ... use p ... */
free(p);
p = NULL;
| Stack | Heap | |
| Speed | Very fast | Slower |
| Size | Small (~1 MB default) | Huge (GBs) |
| Lifetime | Until function returns | Until you free |
| Allocated by | Compiler (auto) | You (malloc) |
| Used for | Locals, parameters | Big or long-lived data |
Dynamic memory is the difference between a toy program and a real one. As soon as your data depends on user input, file size, or the network, you'll need the heap.
int arr[1000000]; // ❌ — 4 MB on the STACK, will overflowThe stack is small (often 1 MB). For anything large or whose size isn't known at compile time, use the heap:
int *arr = malloc(1000000 * sizeof(int)); // ✅ on the heapvoid *malloc(size_t size);size bytes.void * — a pointer to "anything". You assign it to a typed pointer.NULL if allocation fails (out of memory).int *p = malloc(10 * sizeof(int)); // 10 ints = 40 bytes
if (p == NULL) {
fprintf(stderr, "Out of memory\n");
return 1;
}> Use sizeof(*p) instead of sizeof(int) — if you change the type later, you only edit the declaration:
>
>
> int *p = malloc(n * sizeof(*p)); // future-proof
>int *p = calloc(10, sizeof(int)); // 10 ints, all zeroedUse calloc when you need a clean slate (avoids garbage). Slightly slower than malloc because of the zeroing.
int *p = malloc(10 * sizeof(int));
/* ... need more space ... */
int *new_p = realloc(p, 20 * sizeof(int));
if (new_p == NULL) {
/* failure — old p is still valid; don't lose it */
free(p);
return 1;
}
p = new_p; /* only overwrite p AFTER success */realloc may move the block to a new address (if it can't grow in place), so always reassign through a temporary.
Every malloc/calloc/realloc must be matched by exactly one free. Forgetting causes a memory leak:
for (int i = 0; i < 1000; i++) {
int *p = malloc(1024);
/* forgot free(p); → 1 MB leaked per iteration */
}A long-running program with leaks will eventually crash.
char *s = malloc(100);
return; // ❌ leakedfree called twice on the same block.free(p);
free(p); // ❌ undefined — heap corruptionHabit: free(p); p = NULL; — free(NULL) is safe and a no-op.
free.free(p);
p[0] = 5; // ❌ undefined — that memory is now whoever'sSame fix: set to NULL after free, and check before use.
int *p = malloc(10); /* 10 BYTES, not 10 ints! Probably a bug. */
int *p = malloc(10 * sizeof(int)); /* ✅ 10 ints */
char *s = malloc(strlen(name)); /* ❌ no room for '\0' */
char *s = malloc(strlen(name) + 1); /* ✅ room for terminator */Always think in bytes when calling malloc.
| Tool | Catches |
-fsanitize=address (Clang/GCC) | use-after-free, leaks, overflows |
valgrind | leaks, uninit reads, invalid frees |
Static analyzers (clang --analyze) | many issues at compile time |
When learning, always compile with -Wall -Wextra -fsanitize=address -g. The sanitizer prints exactly where the bug is.
int *arr = NULL;
size_t cap = 0, len = 0;void push(int v) {
if (len == cap) {
cap = cap ? cap * 2 : 4;
int *tmp = realloc(arr, cap * sizeof(*arr));
if (!tmp) { /* handle */ return; }
arr = tmp;
}
arr[len++] = v;
}
This is the heart of every "growable list" in every language. Java's ArrayList, Python's list, JS's Array — all built on this idea.
int rows = 3, cols = 4;
int **grid = malloc(rows * sizeof(int *));
for (int r = 0; r < rows; r++) {
grid[r] = malloc(cols * sizeof(int));
}grid[1][2] = 42;
/* Free in reverse */
for (int r = 0; r < rows; r++) free(grid[r]);
free(grid);
| Need | Use |
| n bytes, garbage | malloc(n) |
| n × T, zeroed | calloc(n, sizeof(T)) |
| Resize | realloc(p, new_size) |
| Release | free(p); p = NULL; |
| Always check | if (p == NULL) { ... } |
Master malloc/free and you can build any data structure C is famous for: linked lists, trees, hash tables, graphs.