C programs use two regions of memory: the stack, which automatically manages local variables and shrinks/grows with function calls, and the heap, which the programmer manages manually — this is what <stdlib.h>'s memory functions control.
#include <stdlib.h>
int *arr = malloc(5 * sizeof(int)); // allocate space for 5 ints, uninitialized
if (arr == NULL) { // ALWAYS check — allocation can fail
fprintf(stderr, "Allocation failed\n");
return 1;
}
int *zeroed = calloc(5, sizeof(int)); // like malloc, but zero-initializes the memory
arr = realloc(arr, 10 * sizeof(int)); // resize to hold 10 ints, keeping existing data
if (arr == NULL) { /* handle failure */ }
for (int i = 0; i < 10; i++) arr[i] = i * i;
free(arr); // return the memory to the system
free(zeroed);
arr = NULL; // good practice: avoid leaving a dangling reference
malloc(n) reserves n bytes on the heap and returns a pointer to the start, or NULL if the allocation fails (e.g., the system is out of memory) — checking for NULL before using the pointer is not optional in careful C code. calloc does the same but additionally zero-initializes every byte, useful when uninitialized garbage values would be dangerous. realloc resizes an existing allocation, possibly moving it to a new address if it can't be extended in place — which is exactly why you must always capture its return value (arr = realloc(arr, ...)), since the old pointer may become invalid.
Every successful malloc/calloc/realloc must eventually be matched with exactly one free() call — the heap doesn't clean itself up automatically the way the stack does. The next lesson covers what goes wrong when this rule is broken.