C Programming · Lesson 8 of 10 · Updated ·
Dynamic Memory in C — malloc, calloc, realloc, free and Linked Lists
Arrays need their size at compile time. Dynamic memory lets you decide the size while the program runs — for example, after reading how many students there are.
All functions are in <stdlib.h>.
1. malloc and free
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = 5;
int *arr = malloc(n * sizeof(int)); /* n ints on the heap */
if (arr == NULL) { /* always check */
printf("out of memory\n");
return 1;
}
for (int i = 0; i < n; i++) arr[i] = i * i;
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");
free(arr); /* give it back */
arr = NULL; /* avoid a dangling pointer */
return 0;
}
0 1 4 9 16
2. malloc vs calloc
malloc(bytes) | calloc(count, size) | |
|---|---|---|
| Arguments | total bytes | number of items, size of each |
| Initial values | garbage | all zero |
| Speed | slightly faster | zeroing costs a little |
int *a = calloc(5, sizeof(int)); /* {0, 0, 0, 0, 0} */
3. realloc: grow or shrink
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int cap = 2, n = 0;
int *v = malloc(cap * sizeof(int));
for (int x = 1; x <= 5; x++) {
if (n == cap) { /* full → double the capacity */
cap *= 2;
int *bigger = realloc(v, cap * sizeof(int));
if (!bigger) { free(v); return 1; }
v = bigger;
}
v[n++] = x * 10;
}
printf("n=%d cap=%d last=%d\n", n, cap, v[n - 1]);
free(v);
return 0;
}
n=5 cap=8 last=50
This "double when full" idea is exactly how Python lists and Java ArrayLists grow.
Assign realloc's result to a temporary first: if it fails it returns NULL, and writing
v = realloc(v, …)would lose the original pointer (a leak).
4. Common memory bugs
| Bug | What happens | Prevent it |
|---|---|---|
| Memory leak | memory never freed; long-running programs grow until they crash | free everything you malloc |
| Dangling pointer | using memory after free | set the pointer to NULL after free |
| Double free | freeing the same block twice → crash | NULL after free (free(NULL) is safe) |
| Buffer overflow | writing past the allocated size | track sizes carefully |
Tools like Valgrind and AddressSanitizer find these bugs.
5. A linked list
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
Node *push_front(Node *head, int value) {
Node *n = malloc(sizeof(Node));
n->data = value;
n->next = head;
return n; /* the new node is the new head */
}
int main(void) {
Node *head = NULL;
for (int v = 30; v >= 10; v -= 10) head = push_front(head, v);
for (Node *p = head; p != NULL; p = p->next) printf("%d -> ", p->data);
printf("NULL\n");
while (head) { /* free every node */
Node *next = head->next;
free(head);
head = next;
}
return 0;
}
10 -> 20 -> 30 -> NULL
Linked lists grow one node at a time and insert at the front in O(1); see the DSA linked list tutorial for more.
Common mistakes
- Not checking malloc's return value for NULL.
malloc(n)instead ofmalloc(n * sizeof(int)).- Freeing a list node and then reading
node->next.
Quick recap
- malloc (garbage), calloc (zeros), realloc (resize), free (release) — from stdlib.h.
- Always check for NULL; free what you allocate; set freed pointers to NULL.
- Leaks, dangling pointers, double free and overflows are the classic bugs.
- Linked lists are built from malloc'd nodes connected by pointers.