Linux memory
malloc() — a deep dive

Introduction
Most C programs use malloc and free as if they were simple storage operations. Underneath that API, glibc is making speed-focused decisions about chunk headers, per-thread caches, bins, heap growth, and when to involve the kernel.
This walkthrough is based on experiments on a 32-bit Raspberry Pi. The exact sizes can vary by architecture and glibc version, but the mental model is useful: the allocator tries very hard to satisfy small allocations in user space before it takes the slow path into the kernel.
The hidden header
A request like malloc(8) does not reserve only 8 bytes. glibc wraps the payload in a chunk that carries allocator metadata. On the 32-bit setup used here, the chunk includes a previous-size field, a size-and-flags header, and enough payload room for future free-list pointers.
align(max(min_chunk_size, request + overhead), 8)
// 32-bit example:
// min_chunk_size = 16 bytes
// request = 8 bytes
// overhead includes allocator metadata
Reading one word before the returned pointer exposes the chunk header in this 32-bit experiment:
int *p = malloc(8);
int header = p[-1];
printf("Header: 0x%x\n", header);The header stores the chunk size, but the lowest three bits are reused as flags because aligned chunk sizes leave those bits available.
- P-bit: previous chunk is in use.
- M-bit: chunk came from
mmap. - A-bit: chunk belongs to a non-main arena.
int chunk_size = header & ~0x7;
int p_bit = header & 0x1;
int m_bit = header & 0x2;
int a_bit = header & 0x4;Playground 1
Chunk Layout Explorer
Change the requested payload size and watch the real chunk grow with hidden metadata and alignment padding.
The returned pointer starts at payload. The allocator still owns the bytes before it.
Tcache: the secret pocket
The surprising part is what happens after a small chunk is freed. Modern glibc keeps a per-thread cache called tcache. It lets the thread reuse recently freed chunks without taking heavier locks or immediately merging neighboring free space.
int *p = malloc(8);
int *q = malloc(8);
free(p);
int header = q[-1];Even after freeing p, the next chunk's P-bit can still say the previous chunk is in use. That is intentional: tcache keeps the freed chunk quickly reusable while avoiding immediate coalescing work.


Playground 2
Tcache Bin Simulator
Free small chunks into tcache, then allocate again. The next matching malloc can reuse cached memory without asking the heap.
Allocated
Tcache bin: 32B
Heap top chunk
Initial heap has one top chunk ready to be sliced.
The magic number 7
To force real allocator behavior beyond tcache, the experiment fills the tcache bin. For a size class, tcache commonly holds up to seven chunks. The first seven frees can stay in tcache, so their neighbors still appear publicly busy.
A very small eighth freed chunk may still avoid merging by going through fastbins. To observe coalescing, the experiment uses chunks larger than the fastbin range and then frees enough of them to fill tcache first.
int size = 100;
void *p = malloc(size);
void *q = malloc(size);
void *r = malloc(size);
void *s = malloc(size);
void *t = malloc(size);
void *u = malloc(size);
void *v = malloc(size);
void *w = malloc(size); // 8th chunk
void *x = malloc(size);
free(p);
free(q);
free(r);
free(s);
free(t);
free(u);
free(v);
free(w);Once tcache is full and the chunk is too large for fastbins, the freed chunk can land in the unsorted bin. At that point the allocator acknowledges the neighbor as free, and the next chunk can show P-bit = 0.

The libc leak
A freed chunk in the unsorted bin participates in a linked list. Its forward and backward pointers can point toward allocator structures inside loaded libc. If code later reads that freed memory, it can reveal a high-memory libc address.
unsigned int *ghost_w = (unsigned int *)w;
printf("w[0] (Forward Ptr): 0x%x\n", ghost_w[0]);
printf("w[1] (Backward Ptr): 0x%x\n", ghost_w[1]);This is why use-after-free bugs can be dangerous: a pointer that was logically freed can still contain allocator-written metadata. If an attacker can read that stale memory, it may help defeat address randomization by disclosing where libc is mapped.
Verification with perf
The allocator's two worlds become visible with perf. Repeated small allocations can be served from tcache or fastbins entirely in user space. A much larger allocation may force glibc to ask the kernel to grow the heap with brk or use mmap, depending on threshold configuration.
for (int i = 0; i < 1000; i++) {
free(malloc(10));
}
void *p = malloc(200000);
free(p);
In the observed profile, the large request made __brk and the kernel's __se_sys_brk path visible, while the small request loop was handled without repeated system calls.
Playground 3
Fast Path vs Slow Path Flow
Pick a request type and follow the allocator path from request adjustment to returned pointer.
Round the requested bytes into an allocator-friendly aligned chunk.
Try the current thread's hot freed chunks first.
If tcache misses, look through shared allocator bins for a match.
If no reusable chunk exists, carve space from the current heap top.
Grow the heap only when the local heap cannot satisfy the request.
Return the pointer after metadata, not the true start of the chunk.
A matching chunk is already in tcache, so malloc returns quickly.
malloc flow
The allocator usually starts by adjusting the request into a valid aligned chunk size. It then checks fast user-space pools such as tcache and fastbins. If those do not satisfy the request, it carves from heap space, and only asks the kernel for more memory when the local heap cannot satisfy the allocation.


Summary
- Chunks: returned pointers hide allocator metadata immediately before the payload.
- Tcache: per-thread cached frees are fast and can keep chunks publicly marked as in use.
- Bins: once tcache and fast paths no longer apply, larger freed chunks can move through unsorted-bin logic.
- Security: freed memory may contain allocator pointers, which is why stale reads can leak process layout.
- Kernel calls: glibc avoids
brkandmmapfor small hot allocations whenever it can.