Dynamic Memory with malloc and free
Local variables disappear when their function returns, and array sizes must normally be fixed in advance. When you need memory whose size is only known at run time, or that must outlive a function call, you request it from the heap with malloc — and give it back with free when you're done.
Stack and heap
| Stack (automatic) | Heap (dynamic) | |
|---|---|---|
| Used for | Local variables, function calls | Memory you request with malloc |
| Size | Fixed when compiled; small (often 1–8 MB total) | Chosen at run time; can be very large |
| Lifetime | Ends with the block | Until you call free |
| Managed by | The compiler, automatically | You |
malloc and free
malloc(n) (from <stdlib.h>) reserves n bytes and returns a pointer to them, or NULL if it can't:
#include <stdio.h> #include <stdlib.h> int main(void) { size_t count = 5; int *squares = malloc(count * sizeof *squares); if (squares == NULL) { fprintf(stderr, "out of memory\n"); return 1; } for (size_t i = 0; i < count; i++) { squares[i] = (int) (i * i); } for (size_t i = 0; i < count; i++) { printf("%d ", squares[i]); } printf("\n"); free(squares); squares = NULL; return 0; }
0 1 4 9 16
Points to notice:
count * sizeof *squares— the size in bytes. Usingsizeof *pointerinstead ofsizeof(int)stays correct if the type ever changes.- Check for
NULL. Allocation can fail; using a null pointer would crash. - No cast needed.
mallocreturnsvoid *, which converts to any object pointer type automatically in C. (C++ requires a cast; C doesn't.) - The memory is uninitialised — it contains garbage until you write to it.
freeexactly once, then set the pointer toNULLso it can't be used by accident.
calloc: zeroed memory
calloc(count, size) allocates an array and sets every byte to zero. It also checks that count * size doesn't overflow:
#include <stdio.h> #include <stdlib.h> int main(void) { int *votes = calloc(4, sizeof *votes); if (votes == NULL) return 1; int ballots[] = { 2, 0, 2, 3, 2, 1, 0, 2 }; for (int i = 0; i < 8; i++) { votes[ballots[i]]++; } for (int c = 0; c < 4; c++) { printf("candidate %d: %d votes\n", c, votes[c]); } free(votes); return 0; }
candidate 0: 2 votes candidate 1: 1 votes candidate 2: 4 votes candidate 3: 1 votes
Size decided at run time
#include <stdio.h> #include <stdlib.h> int main(void) { char line[32]; size_t n = 0; printf("How many readings? "); if (!fgets(line, sizeof line, stdin) || sscanf(line, "%zu", &n) != 1 || n == 0) return 1; double *readings = malloc(n * sizeof *readings); if (readings == NULL) return 1; double sum = 0; for (size_t i = 0; i < n; i++) { if (!fgets(line, sizeof line, stdin) || sscanf(line, "%lf", &readings[i]) != 1) { readings[i] = 0; } sum += readings[i]; } printf("\naverage of %zu readings: %.2f\n", n, sum / n); free(readings); return 0; }
4 12.5 14.0 11.75 13.25
How many readings? average of 4 readings: 12.88
Returning allocated memory
Memory from malloc outlives the function that allocated it, so a function can create something and hand it to its caller. The caller then owns it and must free it — document that clearly:
#include <stdio.h> #include <stdlib.h> #include <string.h> // Returns a new string "first last". The caller must free() it. char *full_name(const char *first, const char *last) { size_t len = strlen(first) + 1 + strlen(last) + 1; char *result = malloc(len); if (result == NULL) return NULL; snprintf(result, len, "%s %s", first, last); return result; } int main(void) { char *name = full_name("Grace", "Hopper"); if (name != NULL) { printf("%s (%zu characters)\n", name, strlen(name)); free(name); } return 0; }
Grace Hopper (12 characters)
POSIX and C23 provide strdup, which allocates a copy of a string the same way.
Memory leaks
Memory you never free stays reserved until the program exits — a leak. A short program that exits soon won't notice, but a server or a long loop that leaks a little each time will eventually run out of memory. Every malloc needs exactly one matching free on every path through the code — which is where the goto cleanup pattern from lesson 20 helps.
Rules of thumb
- Use the stack for small, fixed-size, short-lived data; the heap for large, variable-size or long-lived data.
- Always check the result of
malloc/calloc. - Decide who owns each allocation, and free it in one place.
- Never use memory after freeing it, never free it twice, and only free pointers that came from
malloc,callocorrealloc(lesson 38 shows these bugs and the tools that find them). free(NULL)is safe and does nothing.
Try it yourself
Allocate an array for the first n Fibonacci numbers, fill it, print it, and free it.
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377