Dynamic Memory Allocation: malloc and free
After this topic, you will:
- Understand the difference between stack and heap memory.
- Understand why dynamic allocation is necessary.
- Understand why memory leaks occur.
Two Kinds of Memory
The memory used by a program is broadly divided into two areas: the stack and the heap.
Higher address ββββββββββββββββ
β Stack β β Automatically allocated/deallocated when a function is called
β β Downward β
β β
β β
β β Upward β
β Heap β β Programmer explicitly allocates/deallocates
Lower address ββββββββββββββββ| Stack | Heap | |
|---|---|---|
| Allocation | Automatic (on function call) | Manual (malloc) |
| Deallocation | Automatic (on function return) | Manual (free) |
| Size | Small and fixed (usually a few MB) | Large and dynamic (up to RAM limit) |
| Speed | Very fast | Slightly slower |
| Use | Local variables, function arguments | Data of undetermined size |
Why Dynamic Allocation?
When you create a variable on the stack, you must know its size at compile time:
int arr[100]; // 100 elements β determined at compile timeHowever, if the user asks, "How much data do you want to enter?", you won't know until the program is running. This is when you use dynamic allocation:
int n;
printf("How many? ");
scanf("%d", &n);
int *arr = malloc(n * sizeof(int)); // Allocate n elements at runtimemalloc (memory allocation) takes a number of bytes from the heap and returns a pointer to it.
malloc and free
#include <stdlib.h>
// Allocate
int *data = malloc(5 * sizeof(int));
// Use
for (int i = 0; i < 5; i++) {
data[i] = i * 10;
}
// Deallocate
free(data);
data = NULL; // Set to NULL for safetyIf malloc gives you memory, you can use it, and when you're done, you have to return it with free. If you don't call free, that memory will not be returned until the program ends.
Memory Leaks
void process() {
int *data = malloc(1000000 * sizeof(int));
// Process data...
if (error) {
return; // Doesn't call free!
}
free(data);
}If this function exits due to an error, free will not be called. 1 million integers (about 4MB) will not be returned. If this function is called 1000 times, 4GB will leak. This is a memory leak.
Memory leaks are dangerous because they don't immediately crash the program. The program slowly consumes memory, eventually slowing down the entire system and eventually crashing with an OOM (Out of Memory) error. In server programs, this can cause crashes days later.
Solution in High-Level Languages: Garbage Collection
Python, JavaScript, Java, and Go have a garbage collector (GC) that automatically deallocates memory:
def process(): data = [0] * 1000000 # When the function ends, there are no more variables referencing data # β The GC automatically reclaims the memoryThe programmer doesn't have to call free. The GC periodically checks to see if there are any variables that point to the memory and reclaims it if there are none.
There is a cost:
| Manual (C) | Automatic (Python/Java) | |
|---|---|---|
| Memory leak potential | High (programmer error) | Low (handled by GC) |
| Performance | Predictable | May pause during GC execution |
| Code complexity | High (managing free) | Low (no need to worry) |
Rust takes a third approach: it has neither a GC nor manual free. Instead, the compiler tracks the lifetime of variables and automatically inserts deallocation code.
Key Takeaway
The stack is automatically allocated and deallocated, while the heap is managed explicitly by the programmer. Allocate on the heap with
malloc, and return it withfreeβ if you forget, you will have a memory leak. High-level languages automatically solve this problem with GC, but understanding why memory management is necessary is fundamental to computer science.