C in 100 Seconds: malloc and free | Episode 20
Video: C in 100 Seconds: Dynamic Memory — malloc sizeof free | Episode 20 by Taught by Celeste AI - AI Coding Coach
C Dynamic Memory: malloc, sizeof, free
malloc(N * sizeof(int))allocates N ints on the heap.free(p)releases. Every malloc needs a matching free or the memory leaks.
malloc and free are how C handles memory at runtime. Stack allocation (regular variables) is fast but limited; the heap is unlimited but you manage it yourself.
The basic shape
#include <stdio.h>
#include <stdlib.h>
int main() {
int *nums = malloc(5 * sizeof(int));
for (int i = 0; i < 5; i++) {
nums[i] = (i + 1) * 10;
}
for (int i = 0; i < 5; i++) {
printf("nums[%d] = %d\n", i, nums[i]);
}
free(nums);
printf("Memory freed.\n");
return 0;
}
malloc
void *malloc(size_t size);
Allocates size bytes on the heap. Returns a pointer to the allocated memory, or NULL on failure.
The returned pointer is void *. You assign it to your typed pointer; the assignment implicitly casts.
int *nums = malloc(5 * sizeof(int)); // 20 bytes
double *vals = malloc(100 * sizeof(double)); // 800 bytes
char *buf = malloc(256); // 256 bytes
Always multiply: count * sizeof(type). The compiler verifies the type via the assignment, but the math is yours.
sizeof
sizeof(type) returns the byte size of a type at compile time. Cross-platform-safe — handles 32-bit vs 64-bit int differences automatically.
sizeof(int) // typically 4
sizeof(long) // typically 4 or 8
sizeof(double) // 8
sizeof(int *) // 8 on 64-bit, 4 on 32-bit
sizeof(arr) // total bytes of array (only in declaring scope)
Always check for NULL
int *nums = malloc(5 * sizeof(int));
if (nums == NULL) {
fprintf(stderr, "out of memory\n");
return 1;
}
malloc returns NULL when the allocation fails — typically because the system is out of memory. Real code always checks. For demo code, you might skip — but production code without NULL checks is broken.
A tip: define a wrapper.
void *xmalloc(size_t size) {
void *p = malloc(size);
if (p == NULL) {
perror("malloc");
exit(1);
}
return p;
}
xmalloc aborts on failure — useful for "if we can't allocate, we're done anyway" scenarios.
free
free(nums);
nums = NULL; // good practice
free(p) releases the memory p points to. After free, the pointer is invalid — accessing it is undefined behavior.
Setting to NULL after free isn't required, but it converts use-after-free into a NULL-pointer crash, which is usually easier to debug than silent corruption.
Indexing the heap pointer
int *nums = malloc(5 * sizeof(int));
nums[0] = 10;
nums[1] = 20;
*(nums + 2) = 30;
After malloc, nums is just a pointer. You use [] indexing or pointer arithmetic — same as arrays. C doesn't distinguish "this came from malloc" vs "this is a stack array."
Stack vs heap
void demo() {
int stack_arr[100]; // on the stack — fast, automatic cleanup
int *heap_arr = malloc(100 * sizeof(int)); // on the heap — manual cleanup
// ...
free(heap_arr);
}
| Stack | Heap |
|---|---|
| Fast allocation (one register adjustment) | Slower (free-list lookup) |
| Limited size (typical default ~8 MB) | Limited only by available RAM |
| Automatic cleanup | Manual free required |
| Local lifetime (destroyed at scope end) | Lives until free |
| Size known at compile time (mostly) | Size can be runtime-decided |
Use stack for small, fixed-size, scope-limited data. Use heap for big, runtime-sized, or longer-lived data.
Why not always use the heap?
- Cost.
malloc/freeare slow compared to stack allocation. Hot loops should avoid them. - Risk. Forgetting to free leaks memory. Freeing twice is undefined behavior. Pointers can go stale.
- Fragmentation. Long-running programs that malloc/free aggressively can fragment the heap.
For most code, prefer stack allocation when it works. Reach for malloc when you need:
- Size known only at runtime.
- Data that outlives the current function.
- Large allocations that would blow the stack.
sizeof on an allocated pointer
int *p = malloc(10 * sizeof(int));
size_t bytes = sizeof(p); // 8 — pointer size
size_t elements = ??? // can't know — sizeof has no idea
Once you have a pointer to heap memory, the size of the allocation is lost as far as the C language is concerned. You must track the size yourself:
typedef struct {
int *data;
size_t size;
} IntList;
IntList list;
list.size = 10;
list.data = malloc(list.size * sizeof(int));
malloc returns uninitialized memory
int *nums = malloc(5 * sizeof(int));
printf("%d\n", nums[0]); // garbage
malloc doesn't zero the memory. Whatever was there before is still there.
For zero-initialized memory, use calloc (next episode).
Common mistakes
Forgetting to free. Memory leak. For a long-running program, eventually OOM. Episode 22 dives into this.
Freeing twice. Double-free is undefined behavior — usually corrupts the heap and crashes later in a confusing place.
Using after free. free(p); printf("%d", *p); — undefined. Sometimes still works (the memory hasn't been reused yet), sometimes catastrophic.
malloc(N) instead of malloc(N * sizeof(int)). Allocates N bytes, not N ints. Reading arr[1] reads the second byte, not the second int.
Forgetting the NULL check. Allocation fails; pointer is NULL; dereference crashes.
Mixing malloc and new (in C++). Each must be paired with its corresponding free/delete. Don't delete a malloc'd pointer.
Not tracking the allocated size. No way to recover; you have to remember.
What's next
Episode 21: calloc and realloc. Variants for zero-initialized memory and resizing.
Recap
malloc(N * sizeof(type)) allocates on the heap; returns void * that you assign to a typed pointer. Always check for NULL. free(p) releases. Every malloc needs exactly one free. Set pointer to NULL after free for safety. Heap memory is uninitialized — use calloc for zeros. The size is lost; track it yourself. Stack allocation is faster and automatic; reach for the heap when stack won't do.
Next episode: calloc and realloc.