Static allocation requires knowing data size at compile time. Dynamic allocation lets you request memory at runtime, enabling variable-sized data structures and flexible lifetimes.
why_dynamic.c
C
1
#include <stdio.h>
2
#include <stdlib.h>
3
4
int main(void) {
5
int n;
6
printf("How many numbers? ");
7
scanf("%d", &n);
8
9
int *arr = malloc(n * sizeof(int));
10
if (!arr) { fprintf(stderr, "Allocation failed\n"); return 1; }
11
for (int i = 0; i < n; i++) arr[i] = i * i;
12
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
13
printf("\n");
14
free(arr);
15
return 0;
16
}
Heap vs Stack
The stack is for local variables — fast, limited, auto-managed. The heap is for dynamic allocation — slower, large, manually managed.
realloc() changes the size of a previous allocation. It may move memory if in-place expansion is impossible. Use a temporary variable to avoid losing the original pointer.
realloc.c
C
1
#include <stdio.h>
2
#include <stdlib.h>
3
4
int main(void) {
5
int cap = 2, count = 0;
6
int *arr = (int *)malloc(cap * sizeof(int));
7
if (!arr) return 1;
8
9
for (int i = 0; i < 10; i++) {
10
if (count >= cap) {
11
cap *= 2;
12
int *tmp = (int *)realloc(arr, cap * sizeof(int));
13
if (!tmp) { free(arr); return 1; }
14
arr = tmp;
15
printf("Grew to capacity %d\n", cap);
16
}
17
arr[count++] = i * 100;
18
}
19
20
printf("Final: ");
21
for (int i = 0; i < count; i++) printf("%d ", arr[i]);
22
printf("\n");
23
free(arr);
24
return 0;
25
}
⚠
warning
Never write: arr = realloc(arr, new_size). If realloc fails, you lose the original pointer. Always use a temporary variable first.
free() — Release Memory
free() releases memory back to the heap. After freeing, the pointer becomes invalid. Set it to NULL. free(NULL) is safe per the C standard.
free.c
C
1
#include <stdio.h>
2
#include <stdlib.h>
3
4
int main(void) {
5
int *p = (int *)malloc(sizeof(int));
6
*p = 42;
7
printf("Before free: %d\n", *p);
8
free(p);
9
p = NULL; /* Prevent dangling pointer */
10
if (p == NULL) printf("p is NULL after free\n");
11
return 0;
12
}
Rule
Description
Free exactly once
Each allocation freed exactly one time
Free in correct order
Free inner allocations first for nested structures
Set to NULL after free
Prevents use-after-free bugs
free(NULL) is safe
Explicitly allowed by the C standard
Dynamic Arrays
A dynamic array grows at runtime using the doubling strategy. Allocate initial capacity, double when full. Amortized O(1) insertion like std::vector.
dynamic_array.c
C
1
#include <stdio.h>
2
#include <stdlib.h>
3
4
typedef struct {
5
int *data; int size; int capacity;
6
} DynArray;
7
8
DynArray *dynarray_create(int cap) {
9
DynArray *da = malloc(sizeof(DynArray));
10
if (!da) return NULL;
11
da->data = malloc(cap * sizeof(int));
12
if (!da->data) { free(da); return NULL; }
13
da->size = 0; da->capacity = cap;
14
return da;
15
}
16
17
void dynarray_push(DynArray *da, int value) {
18
if (da->size >= da->capacity) {
19
int *tmp = realloc(da->data, da->capacity * 2 * sizeof(int));
20
if (!tmp) return;
21
da->data = tmp; da->capacity *= 2;
22
}
23
da->data[da->size++] = value;
24
}
25
26
void dynarray_free(DynArray *da) {
27
if (da) { free(da->data); free(da); }
28
}
29
30
int main(void) {
31
DynArray *da = dynarray_create(4);
32
for (int i = 0; i < 20; i++) dynarray_push(da, i * 10);