A structured lesson workspace with readable content, hands-on examples, and a clean path to completion.
Stack memory is automatic. You declare a variable, it exists. The function returns, it vanishes. You do not choose when it is freed. You do not choose how long it lives. Dynamic memory is different. You ask the operating system for a block of heap memory at runtime. You decide when to give it back. If you forget to give it back, it leaks. If you give it back twice, you corrupt the heap. If you use it after giving it back, you read garbage or crash. This is the bargain: total control in exchange for total responsibility.
#include <stdio.h>#include <stdlib.h>int main(void) {int n = 5;int *arr = malloc(n * sizeof(int));if (arr == NULL) {fprintf(stderr, "malloc failed\n");return 1;}for (int i = 0; i < n; i++) {arr[i] = (i + 1) * 10;}printf("Dynamic array: ");for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");free(arr);arr = NULL;return 0;}
malloc returns a void pointer to a block of at least the requested size, or NULL if the allocation fails. You cast it (or not; we debate that in a later lesson) and use it as an array. When you are done, free returns the block to the heap. Setting arr = NULL after free is a defensive habit. It turns a use-after-free into a deterministic NULL dereference crash rather than silent corruption.
#include <stdio.h>#include <stdlib.h>int main(void) {int *leak = malloc(sizeof(int));*leak = 42;int *double_free = malloc(sizeof(int));*double_free = 99;free(double_free);free(double_free);int *use_after = malloc(sizeof(int));*use_after = 7;free(use_after);printf("After free: %d\n", *use_after);return 0;}
ASan catches the double-free immediately and aborts. Without ASan, the double-free corrupts the heap allocator's internal data structures. The corruption might not manifest until a later malloc or free call, possibly in a completely different part of the program. The use-after-free reads whatever the allocator placed in the freed block. The leak silently consumes memory until the process runs out. All three are among the most exploited vulnerability classes in C software.
Every malloc must have exactly one corresponding free. The question is: who calls it? The function that allocated, or the caller? You must decide and document. A common pattern: the allocator function returns a pointer, and the caller is responsible for freeing it. The function's documentation states this. If the contract is unclear, you get either a leak (nobody frees) or a double-free (both free).
Run every program you write with -fsanitize=address during development. It catches leaks, double-frees, use-after-free, buffer overflows, and stack corruption. The runtime overhead is roughly 2x. You do not ship with it. You develop with it. It is the single most effective debugging tool in C.
Dynamic memory is what separates C from languages that manage memory for you. The control is real. The responsibility is real. Every allocation is a decision about lifetime, ownership, and cleanup. Get those decisions right and your program is efficient and predictable. Get them wrong and you have a security vulnerability. Next: malloc vs calloc, and whether the zeroing matters.
Finish the lesson once you have worked through the material. This awards ★ 30 XP.