Before any policy questions β which chunk should serve a request? β come the mechanisms every allocator shares: cutting chunks, merging chunks, remembering sizes, and the neat trick of storing the free list in the very memory it tracks.
Splitting and Coalescing
Recall the 30-byte heap and its two-entry free list. A request bigger than 10 fails; a request for exactly 10 fits either chunk. But a request for 1 byte? The allocator performs splitting splitting Allocator mechanism: when a request is smaller than any free chunk, split one chunk in two β return the first piece to the caller, keep the remainder on the free list. defined in ch. 17 β open in glossary : pick a chunk, cut it in two, return the first piece, keep the rest listed. Serving that 1 byte from the second chunk:
Splitting: malloc(1) returns address 20; the list barely notices
len:10βaddr:20
len:10βNULL
len:10βaddr:21
len:9βNULL
The corollary mechanism runs the other way. Suppose the middle 10 bytes (the used ones) are freed. Add them to the list without thinking and the whole heap is free β but the list says three shards:
Coalescing: without it, a fully-free heap fails a 20-byte request
len:10βaddr:0
len:10βaddr:20
len:10βNULL
len:30βNULL
Coalescing coalescing Allocator mechanism: when memory is freed, merge it with any address-adjacent free chunks into one larger chunk β without it, a fully-free heap can still look fragmented into useless pieces. defined in ch. 17 β open in glossary fixes it at free time: look at the addresses of the returning chunk and its neighbors; if they sit adjacent, merge them into one larger chunk. Large extents stay visible to the list, not just theoretically present.
Tracking The Size Of Allocated Regions
free(void *ptr) takes no size β so the library stashes one. Most
allocators keep a small header allocation header A small block the allocator stores just before each handed-out chunk (minimally its size, often a magic number for integrity), letting free(ptr) deduce the region's size β so a request for N bytes really searches for N + header bytes.
defined in ch. 17 β open in glossary
in
memory just before each handed-out chunk:
typedef struct {
int size;
int magic;
} header_t;
Figures 17.1 + 17.2: an allocated region plus its header β ptr = malloc(20)
On free(ptr), simple pointer arithmetic recovers the header:
void free(void *ptr) {
header_t *hptr = (header_t *) ptr - 1;
assert(hptr->magic == 1234567); // integrity check
// freed region size = hptr->size + sizeof(header_t)
...
}
Note the small but critical detail: the freed regionβs size is the header plus the payload. So when a user requests N bytes, the library doesnβt hunt for a chunk of size N β it hunts for N + the header size. Request 100, consume 108.
Embedding A Free List
The list needs nodes; nodes need memory; and the one thing a memory
allocator cannot do is call malloc() for its own bookkeeping. The
escape: build the list inside the free space itself.
typedef struct __node_t {
int size;
struct __node_t *next;
} node_t;
// mmap() returns a pointer to a chunk of free space
node_t *head = mmap(NULL, 4096, PROT_READ|PROT_WRITE,
MAP_ANON|MAP_PRIVATE, -1, 0);
head->size = 4096 - sizeof(node_t);
head->next = NULL;
Each free chunkβs own first bytes describe it. Step through the bookβs 4KB heap β init, three allocations, a free, a mess, and the merge that heals it:
1Initialize: one node, planted in the space it describes (Fig 17.3)
mmap() hands the library 4KB at virtual address 16KB. The library writes a node_t at the very start of that space: size 4088 (4096 minus the 8-byte node) and next = NULL. The free list IS the free space.
Growing The Heap
And if the heap simply runs out? The simplest answer: fail. Returning NULL is an honorable approach β you tried, and though you failed, you fought the good fight. Most traditional allocators, though, start small and ask the OS for more: the sbrk() negotiation from the segmentation chapter β the OS finds free physical pages, maps them into the address space, and returns the new end of the heap. A bigger arena, same mechanisms.
Check yourself
1.The free list holds two 10-byte chunks (at 0 and 20). A request for ONE byte arrives, served from the second chunk. What does the allocator do, and what does the list look like after?
2.The middle 10 bytes of the 30-byte heap are freed, and the allocator naΓ―vely prepends the chunk to its list. The whole heap is now free β so why does a request for 20 bytes still fail?
3.How does free(ptr) discover the size of the region being freed β and what's the small-but-critical consequence for malloc(N)?
4.The free list needs nodes. Where does the memory for the list's own nodes come from?
5.In the 4KB walkthrough, the program frees the MIDDLE of three 100-byte chunks by calling free(16500). Why 16500 exactly?