Every modern language with a garbage collector exists partly to solve the exact problems C forces you to handle manually. Understanding manual memory management — even if you never write production C — makes you a sharper programmer in literally every other language.
Stack vs Heap
Stack memory (local variables) is automatically reclaimed the moment a function returns — fast, but limited in size and lifetime. Heap memory, allocated explicitly, persists until you free it yourself and can be sized dynamically at runtime.
#include <stdlib.h>
int stack_example() {
int x = 42; // lives on the stack, gone when function returns
return x;
}
int* heap_example() {
int* p = malloc(sizeof(int)); // lives on the heap until freed
*p = 42;
return p; // safe to return; caller now owns this memory
}
The Two Classic Bugs
// Memory leak: allocated memory never freed
void leak() {
int* p = malloc(100 * sizeof(int));
// ... p goes out of scope without a free(p) call
// this memory is now unreachable and permanently lost
}
// Use-after-free: accessing memory after it's been released
void use_after_free() {
int* p = malloc(sizeof(int));
free(p);
*p = 5; // undefined behavior — this memory may be reused by anything
}
The Fix: Match Every malloc with a free
void correct_usage() {
int* buffer = malloc(100 * sizeof(int));
if (buffer == NULL) {
return; // always check malloc's return value
}
// ... use buffer ...
free(buffer);
buffer = NULL; // prevents accidental use-after-free later in the function
}
Why This Still Matters in a GC World
Garbage collectors solve reclaiming memory, but they don’t solve holding onto references longer than needed (the GC equivalent of a leak) or resource lifecycle bugs with file handles, database connections, and locks — the mental model of “who owns this, and when does it get released” transfers directly, even in Python, Java, or JavaScript.