C — Dynamic Memory Allocation
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.
| 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 | } |
The stack is for local variables — fast, limited, auto-managed. The heap is for dynamic allocation — slower, large, manually managed.
| 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 | |
| 15 | int global_var; /* BSS segment */ |
| 16 | |
| 17 | void func(void) { |
| 18 | int local = 10; /* Stack */ |
| 19 | int *heap = malloc(sizeof(int)); /* Heap */ |
| 20 | *heap = 42; |
| 21 | free(heap); |
| 22 | } |
| 23 | |
| 24 | int main(void) { func(); return 0; } |
note
malloc() allocates a contiguous block and returns void*. The memory is uninitialized. Always check for NULL.
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | |
| 4 | int 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
calloc() allocates memory for an array of elements and initializes all bytes to zero. It checks for overflow in the multiplication.
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | |
| 4 | int 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 | } |
| Function | Syntax | Initializes? | Use When |
|---|---|---|---|
malloc | malloc(size) | No (garbage) | You will initialize immediately |
calloc | calloc(count, size) | Yes (zeros) | You need zeroed memory |
realloc | realloc(ptr, size) | Preserves data | Resizing previous 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.
| 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
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.
| 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 |
A dynamic array grows at runtime using the doubling strategy. Allocate initial capacity, double when full. Amortized O(1) insertion like std::vector.
| 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); |
| 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 | } |
Two approaches: array of pointers (each row separately) or flat contiguous block. Flat is more cache-friendly.
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | |
| 4 | /* Flat contiguous block (cache-friendly) */ |
| 5 | int **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 | |
| 13 | int 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 | } |
strdup() allocates and copies a string. Building strings dynamically involves realloc as the string grows.
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | #include <string.h> |
| 4 | |
| 5 | char *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 | |
| 12 | typedef struct { |
| 13 | char *data; size_t length; size_t capacity; |
| 14 | } StringBuilder; |
| 15 | |
| 16 | StringBuilder *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 | |
| 26 | void 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 | |
| 40 | void sb_free(StringBuilder *sb) { |
| 41 | if (sb) { free(sb->data); free(sb); } |
| 42 | } |
| 43 | |
| 44 | int 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 | } |
A memory leak is allocated memory never freed. In long-running programs, leaks accumulate and exhaust memory.
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | |
| 4 | void leak_forget(void) { |
| 5 | char *s = malloc(100); |
| 6 | /* No free(s) — leaked */ |
| 7 | } |
| 8 | |
| 9 | void leak_overwrite(void) { |
| 10 | char *s = malloc(100); |
| 11 | s = malloc(200); /* First allocation is lost */ |
| 12 | free(s); |
| 13 | } |
| 14 | |
| 15 | int 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
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | #include <string.h> |
| 4 | |
| 5 | void 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 | |
| 13 | void 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 | |
| 20 | void safe_pattern(void) { |
| 21 | char *p = malloc(32); |
| 22 | if (!p) return; |
| 23 | strcpy(p, "data"); |
| 24 | free(p); p = NULL; |
| 25 | } |
| 26 | |
| 27 | int main(void) { safe_pattern(); return 0; } |
| 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 | |
| 7 | int 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 | } |
| Tool | Platform | Detects | Command |
|---|---|---|---|
Valgrind | Linux, macOS | Leaks, invalid reads/writes | valgrind --leak-check=full |
ASan | GCC, Clang | Buffer overflow, use-after-free | -fsanitize=address |
MSan | Clang | Uninitialized memory | -fsanitize=memory |
UBSan | GCC, Clang | Undefined behavior | -fsanitize=undefined |
A complete dynamic array with doubling strategy for amortized O(1) insertions, like std::vector.
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | #include <string.h> |
| 4 | |
| 5 | typedef struct { int *items; int size; int capacity; } ArrayList; |
| 6 | |
| 7 | ArrayList *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 | |
| 16 | int 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 | |
| 26 | void arraylist_free(ArrayList *list) { |
| 27 | if (list) { free(list->items); free(list); } |
| 28 | } |
| 29 | |
| 30 | int 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 | } |
Community
Get help on Slack, Discord or VIP
Stuck on a guide? Join the community and ask.