The Four Allocation Functions
{:.gc-basic}
Basic
#include <stdlib.h>
// malloc: allocate n bytes — contents are UNINITIALIZED (garbage)
void *malloc(size_t size);
// calloc: allocate n elements of given size — contents ZEROED
void *calloc(size_t nmemb, size_t size);
// realloc: resize an existing allocation
void *realloc(void *ptr, size_t new_size);
// free: release allocation back to the heap
void free(void *ptr);
Basic Allocation Pattern
// Allocate memory for 10 ints
int *arr = malloc(10 * sizeof(int));
if (arr == NULL) {
fprintf(stderr, "malloc failed\n");
exit(EXIT_FAILURE);
}
// Use it
for (int i = 0; i < 10; i++)
arr[i] = i * i;
// Always free when done
free(arr);
arr = NULL; // good practice: prevents use-after-free bugs
calloc — Zeroed Allocation
// Safer for arrays of structs — everything starts at zero
SensorRecord *records = calloc(100, sizeof(SensorRecord));
if (!records) { perror("calloc"); exit(1); }
// records[0].temperature == 0.0f, etc.
free(records);
realloc — Dynamic Resizing
int *buf = malloc(10 * sizeof(int));
int cap = 10, len = 0;
// Dynamically grow the buffer
for (int i = 0; i < 25; i++) {
if (len == cap) {
cap *= 2;
int *tmp = realloc(buf, cap * sizeof(int));
if (!tmp) { free(buf); exit(1); } // realloc failure doesn't free old buf!
buf = tmp;
}
buf[len++] = i;
}
free(buf);
Never do:
buf = realloc(buf, new_size)— if realloc returns NULL, you lose the original pointer and leak the memory.
Intermediate: Common Allocation Bugs
{:.gc-mid}
Intermediate
Memory Leak
void process_file(const char *path) {
char *buf = malloc(1024);
FILE *fp = fopen(path, "r");
if (!fp) return; // BUG: buf is leaked — should free(buf) first
fread(buf, 1, 1024, fp);
fclose(fp);
free(buf);
}
Use After Free
int *p = malloc(sizeof(int));
*p = 42;
free(p);
printf("%d\n", *p); // UNDEFINED BEHAVIOUR — p is now a dangling pointer
p = NULL; // always null the pointer after freeing
Double Free
int *p = malloc(sizeof(int));
free(p);
free(p); // heap corruption — may crash immediately or silently corrupt
Buffer Overflow
int *arr = malloc(5 * sizeof(int));
for (int i = 0; i <= 5; i++) // BUG: writes to arr[5] — out of bounds
arr[i] = i;
free(arr);
Wrong Size
int **matrix = malloc(N * sizeof(int)); // BUG: should be sizeof(int *)
// On 64-bit systems, sizeof(int)=4 but sizeof(int *)=8 — only half the memory allocated
Detecting Problems with Tools
{:.gc-mid}
Valgrind
gcc -g -o program program.c
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./program
Sample output:
==12345== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck.so)
==12345== at 0x10869F: process_file (program.c:5)
==12345== at 0x1086B5: main (program.c:15)
==12345== Invalid read of size 4
==12345== at 0x108650: main (program.c:9)
AddressSanitizer (ASan)
Faster than Valgrind (2x overhead vs 20x), catches: heap/stack/global buffer overflow, use-after-free, double-free.
gcc -fsanitize=address -fsanitize=undefined -g -o program program.c
./program
Sample ASan output:
==12345==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000010
READ of size 4 at 0x602000000010 thread T0
#0 0x401234 in main program.c:9
Advanced: Pool Allocator
{:.gc-adv}
Advanced
General-purpose malloc is too slow and non-deterministic for real-time embedded systems. A pool allocator pre-allocates a fixed block of memory and serves allocations in O(1) with no fragmentation.
#define POOL_SIZE 64
#define BLOCK_SIZE sizeof(SensorRecord)
typedef struct Block {
struct Block *next;
} Block;
static char pool_mem[POOL_SIZE * BLOCK_SIZE];
static Block *pool_free = NULL;
void pool_init(void) {
pool_free = NULL;
for (int i = 0; i < POOL_SIZE; i++) {
Block *b = (Block *)(pool_mem + i * BLOCK_SIZE);
b->next = pool_free;
pool_free = b;
}
}
void *pool_alloc(void) {
if (!pool_free) return NULL; // pool exhausted
Block *b = pool_free;
pool_free = b->next;
return b;
}
void pool_free_block(void *ptr) {
Block *b = (Block *)ptr;
b->next = pool_free;
pool_free = b;
}
Usage:
pool_init();
SensorRecord *r = pool_alloc();
r->temperature = 25.3f;
pool_free_block(r);
Heap Internals
Understanding how malloc works helps you debug fragmentation:
Heap (managed by libc malloc):
┌────────────────────────────────────────┐
│ [used: 40B] [used: 16B] [free: 200B] │ → sbrk()/mmap() extends this
└────────────────────────────────────────┘
- Small allocs (< 128 KB): served from the heap via brk()
- Large allocs (≥ 128 KB): get their own mmap() region
- free() coalesces adjacent free blocks to fight fragmentation
// Check heap statistics
#include <malloc.h>
struct mallinfo2 info = mallinfo2();
printf("Arena: %zu bytes\n", info.arena);
printf("In use: %zu bytes\n", info.uordblks);
printf("Free: %zu bytes\n", info.fordblks);
Interview Q&A
{:.gc-iq}
Interview Q&A
Q1 — Basic: What is the difference between malloc and calloc?
malloc(n)allocatesnbytes with uninitialized contents (garbage from previous allocations or OS).calloc(count, size)allocatescount * sizebytes and zeros the memory.callocis safer for arrays of structs (no accidental reads of garbage) and is often implemented via the OS’smmapwhich already returns zeroed pages, making it not necessarily slower thanmalloc + memset.
Q2 — Intermediate: Why should you use a temporary pointer with realloc?
If
reallocfails, it returnsNULLand leaves the original allocation untouched. If you writebuf = realloc(buf, new_size)and it returnsNULL, you’ve overwritten your only pointer to the old buffer, causing a leak. Always use:tmp = realloc(buf, new_size); if (tmp) buf = tmp; else { /* handle error */ }.
Q3 — Intermediate: What is heap fragmentation and why does it matter for embedded systems?
Fragmentation occurs when repeated alloc/free cycles leave many small unusable gaps between allocated blocks. Even if the total free bytes are sufficient, a large contiguous allocation may fail. In embedded systems with limited RAM, fragmentation can cause sudden
mallocfailures hours or days into operation. Solutions: pool allocators, slab allocators, or avoiding dynamic allocation entirely after initialization.
Q4 — Advanced: How does a pool allocator differ from a general-purpose allocator, and when would you choose it?
A pool allocator pre-allocates a fixed array of equal-size blocks and maintains a free list. Allocation and deallocation are O(1) with no fragmentation (all blocks are the same size). Choose it when: (1) you allocate/free objects of a single known size at high frequency, (2) you need deterministic real-time performance (no
mallocjitter), (3) you want to bound memory usage at compile time. Downside: wastes memory if pool is rarely fully used.
References
{:.gc-ref}
References
| Resource | Link |
|---|---|
man 3 malloc |
malloc, calloc, realloc, free |
man 3 mallinfo2 |
Heap statistics |
| Valgrind documentation | valgrind.org |
| AddressSanitizer | github.com/google/sanitizers |
| Doug Lea’s malloc (dlmalloc) | Reference implementation of malloc |