|$ curl https://forge-ai.dev/api/markdown?path=docs/c/dynamic-memory
$cat docs/c-—-dynamic-memory-allocation.md
updated Recently·40 min read·published

C — Dynamic Memory Allocation

CDynamic MemorymallocIntermediateIntermediate🎯Free Tools
Why Dynamic Memory?

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
4int 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.

heap_vs_stack.c
C
1/*
2 STACK HEAP
3 +------------------------+ +------------------------+
4 | Grows downward | | Grows upward |
5 | Fast alloc/dealloc | | Slower alloc |
6 | Limited size (MB) | | Large size (GB) |
7 | Auto-managed (LIFO) | | Manual management |
8 | Lifetime: scope-based | | Lifetime: until free() |
9 +------------------------+ +------------------------+
10*/
11
12#include <stdio.h>
13#include <stdlib.h>
14
15int global_var; /* BSS segment */
16
17void func(void) {
18 int local = 10; /* Stack */
19 int *heap = malloc(sizeof(int)); /* Heap */
20 *heap = 42;
21 free(heap);
22}
23
24int main(void) { func(); return 0; }
📝

note

The stack typically has a limit of 1-8 MB. Use the heap for anything larger than a few KB, or when the size is not known at compile time.
malloc() — Allocate Memory

malloc() allocates a contiguous block and returns void*. The memory is uninitialized. Always check for NULL.

malloc.c
C
1#include <stdio.h>
2#include <stdlib.h>
3
4int main(void) {
5 int *p = (int *)malloc(sizeof(int));
6 if (!p) { fprintf(stderr, "malloc failed\n"); return 1; }
7 *p = 42;
8 printf("*p = %d\n", *p);
9 free(p);
10
11 int *arr = (int *)malloc(10 * sizeof(int));
12 if (!arr) { fprintf(stderr, "malloc failed\n"); return 1; }
13 for (int i = 0; i < 10; i++) arr[i] = i * 10;
14 free(arr);
15 return 0;
16}

info

Cast is not required in C but is required in C++. Always check malloc's return value — on failure it returns NULL.
calloc() — Allocate and Zero-Initialize

calloc() allocates memory for an array of elements and initializes all bytes to zero. It checks for overflow in the multiplication.

calloc.c
C
1#include <stdio.h>
2#include <stdlib.h>
3
4int main(void) {
5 int *a = (int *)malloc(5 * sizeof(int));
6 printf("malloc: ");
7 for (int i = 0; i < 5; i++) printf("%d ", a[i]); /* garbage */
8 printf("\n"); free(a);
9
10 int *b = (int *)calloc(5, sizeof(int));
11 printf("calloc: ");
12 for (int i = 0; i < 5; i++) printf("%d ", b[i]); /* zeros */
13 printf("\n"); free(b);
14
15 int rows = 3, cols = 4;
16 int **matrix = (int **)calloc(rows, sizeof(int *));
17 for (int i = 0; i < rows; i++)
18 matrix[i] = (int *)calloc(cols, sizeof(int));
19 printf("matrix[1][2] = %d\n", matrix[1][2]); /* 0 */
20 for (int i = 0; i < rows; i++) free(matrix[i]);
21 free(matrix);
22 return 0;
23}
FunctionSyntaxInitializes?Use When
mallocmalloc(size)No (garbage)You will initialize immediately
calloccalloc(count, size)Yes (zeros)You need zeroed memory
reallocrealloc(ptr, size)Preserves dataResizing previous allocation
realloc() — Resize Allocation

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
4int 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
4int 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}
RuleDescription
Free exactly onceEach allocation freed exactly one time
Free in correct orderFree inner allocations first for nested structures
Set to NULL after freePrevents use-after-free bugs
free(NULL) is safeExplicitly 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
4typedef struct {
5 int *data; int size; int capacity;
6} DynArray;
7
8DynArray *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
17void 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
26void dynarray_free(DynArray *da) {
27 if (da) { free(da->data); free(da); }
28}
29
30int main(void) {
31 DynArray *da = dynarray_create(4);
32 for (int i = 0; i < 20; i++) dynarray_push(da, i * 10);
33 printf("Size: %d, Cap: %d\n", da->size, da->capacity);
34 for (int i = 0; i < da->size; i++) printf("%d ", da->data[i]);
35 printf("\n");
36 dynarray_free(da);
37 return 0;
38}
2D Dynamic Arrays

Two approaches: array of pointers (each row separately) or flat contiguous block. Flat is more cache-friendly.

dynamic_2d.c
C
1#include <stdio.h>
2#include <stdlib.h>
3
4/* Flat contiguous block (cache-friendly) */
5int **alloc_matrix(int rows, int cols) {
6 int **m = (int **)malloc(rows * sizeof(int *));
7 int *data = (int *)calloc(rows * cols, sizeof(int));
8 for (int i = 0; i < rows; i++)
9 m[i] = data + i * cols;
10 return m;
11}
12
13int main(void) {
14 int **m = alloc_matrix(3, 4);
15 m[1][2] = 42;
16 printf("m[1][2] = %d\n", m[1][2]);
17 free(m[0]); free(m);
18 return 0;
19}
Dynamic Strings

strdup() allocates and copies a string. Building strings dynamically involves realloc as the string grows.

dynamic_strings.c
C
1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4
5char *my_strdup(const char *s) {
6 size_t len = strlen(s) + 1;
7 char *dup = (char *)malloc(len);
8 if (dup) memcpy(dup, s, len);
9 return dup;
10}
11
12typedef struct {
13 char *data; size_t length; size_t capacity;
14} StringBuilder;
15
16StringBuilder *sb_create(void) {
17 StringBuilder *sb = malloc(sizeof(StringBuilder));
18 if (!sb) return NULL;
19 sb->capacity = 64; sb->length = 0;
20 sb->data = malloc(sb->capacity);
21 if (!sb->data) { free(sb); return NULL; }
22 sb->data[0] = '\0';
23 return sb;
24}
25
26void sb_append(StringBuilder *sb, const char *text) {
27 size_t len = strlen(text);
28 size_t new_len = sb->length + len;
29 if (new_len + 1 > sb->capacity) {
30 size_t new_cap = sb->capacity * 2;
31 while (new_cap < new_len + 1) new_cap *= 2;
32 char *tmp = realloc(sb->data, new_cap);
33 if (!tmp) return;
34 sb->data = tmp; sb->capacity = new_cap;
35 }
36 memcpy(sb->data + sb->length, text, len + 1);
37 sb->length = new_len;
38}
39
40void sb_free(StringBuilder *sb) {
41 if (sb) { free(sb->data); free(sb); }
42}
43
44int main(void) {
45 StringBuilder *sb = sb_create();
46 sb_append(sb, "Hello, ");
47 sb_append(sb, "World!");
48 printf("String: %s\n", sb->data);
49 sb_free(sb);
50 return 0;
51}
Memory Leaks

A memory leak is allocated memory never freed. In long-running programs, leaks accumulate and exhaust memory.

memory_leaks.c
C
1#include <stdio.h>
2#include <stdlib.h>
3
4void leak_forget(void) {
5 char *s = malloc(100);
6 /* No free(s) — leaked */
7}
8
9void leak_overwrite(void) {
10 char *s = malloc(100);
11 s = malloc(200); /* First allocation is lost */
12 free(s);
13}
14
15int fixed_early_return(int error) {
16 int *a = malloc(sizeof(int) * 100);
17 if (a == NULL) return -1;
18 if (error) { free(a); return -1; }
19 free(a);
20 return 0;
21}

best practice

Every malloc must have exactly one free. Document ownership — who allocates, who frees. Use Valgrind to catch leaks.
Double Free and Use-After-Free
double_free.c
C
1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4
5void double_free_demo(void) {
6 char *p = malloc(32);
7 strcpy(p, "secret");
8 free(p);
9 /* free(p); /* UNDEFINED BEHAVIOR */
10 p = NULL;
11}
12
13void use_after_free_demo(void) {
14 char *p = malloc(32);
15 strcpy(p, "sensitive");
16 free(p);
17 /* printf("%s\n", p); /* UNDEFINED BEHAVIOR */
18}
19
20void safe_pattern(void) {
21 char *p = malloc(32);
22 if (!p) return;
23 strcpy(p, "data");
24 free(p); p = NULL;
25}
26
27int main(void) { safe_pattern(); return 0; }
Tools for Finding Memory Issues
tools.c
C
1/* AddressSanitizer: gcc -fsanitize=address -g prog.c */
2/* Valgrind: valgrind --leak-check=full ./prog */
3
4#include <stdio.h>
5#include <stdlib.h>
6
7int main(void) {
8 int *p = malloc(sizeof(int) * 10);
9 /* p[10] = 42; /* ASan: heap-buffer-overflow */
10 free(p);
11 /* printf("%d\n", p[0]); /* ASan: heap-use-after-free */
12 return 0;
13}
ToolPlatformDetectsCommand
ValgrindLinux, macOSLeaks, invalid reads/writesvalgrind --leak-check=full
ASanGCC, ClangBuffer overflow, use-after-free-fsanitize=address
MSanClangUninitialized memory-fsanitize=memory
UBSanGCC, ClangUndefined behavior-fsanitize=undefined
Real Example: Dynamic Array (ArrayList)

A complete dynamic array with doubling strategy for amortized O(1) insertions, like std::vector.

arraylist.c
C
1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4
5typedef struct { int *items; int size; int capacity; } ArrayList;
6
7ArrayList *arraylist_create(int cap) {
8 ArrayList *list = malloc(sizeof(ArrayList));
9 if (!list) return NULL;
10 list->items = malloc(cap * sizeof(int));
11 if (!list->items) { free(list); return NULL; }
12 list->size = 0; list->capacity = cap;
13 return list;
14}
15
16int arraylist_add(ArrayList *list, int item) {
17 if (list->size >= list->capacity) {
18 int *tmp = realloc(list->items, list->capacity * 2 * sizeof(int));
19 if (!tmp) return -1;
20 list->items = tmp; list->capacity *= 2;
21 }
22 list->items[list->size++] = item;
23 return 0;
24}
25
26void arraylist_free(ArrayList *list) {
27 if (list) { free(list->items); free(list); }
28}
29
30int main(void) {
31 ArrayList *list = arraylist_create(2);
32 for (int i = 0; i < 15; i++) arraylist_add(list, i * 10);
33 printf("Size: %d, Cap: %d\n", list->size, list->capacity);
34 arraylist_free(list);
35 return 0;
36}
$Blueprint — Engineering Documentation·Section ID: C-DYNAMIC-MEMORY·Revision: 1.0