All projects

Systems / 2025

Custom Memory Manager

A C dynamic memory allocator built on an address-ordered explicit free list with first-fit placement, coalescing, and eight size bins.

Overview

I implemented a dynamic memory allocator in C to understand what actually happens beneath malloc and free. The allocator manages its own heap region, tracks free blocks in an explicit free list kept in address order, places allocations using first fit, coalesces neighboring free blocks, and organizes available blocks into eight size bins.

Problem and motivation

Allocation is easy to treat as a primitive. It is not one. Every call has to answer where a block should come from, how a freed block rejoins the pool, and how the heap avoids fragmenting into pieces too small to use.

I wanted those decisions in front of me rather than behind a library boundary. Writing the allocator meant choosing a policy for each one and living with the consequences.

Approach

The design centers on an explicit free list: free blocks store their own list pointers, so traversal visits only free memory instead of walking every block in the heap.

Keeping that list sorted by address is what makes coalescing tractable. When a block is freed, its neighbors are already adjacent in the list, so merging is a local operation rather than a search.

Placement uses first fit: the first block large enough wins. First fit trades a small amount of space efficiency for predictable, short search paths, which suited an allocator meant to be reasoned about and measured.

Architecture

The allocator separates three responsibilities:

  1. Block metadata: each block records its size and whether it is in use, so the allocator can move between neighbors and validate its own structure.
  2. The free list: an address-ordered list of available blocks, with insertion positioned to preserve that ordering.
  3. Size bins: eight bins group free blocks by size class, so a request can start its search near blocks that are plausibly large enough instead of from the front of the list.

Splitting a block that is larger than requested returns the remainder to the pool; coalescing merges adjacent free blocks so that remainder does not become permanently unusable.

Challenges and tradeoffs

The hardest part was invariant discipline. An allocator is a data structure that describes the memory it lives in, so a single incorrect size or stale pointer corrupts the structure used to detect the corruption. I leaned on tight, explicit block operations and checked the list’s ordering assumptions rather than trusting them.

The bins and the address ordering pull in different directions: bins want blocks grouped by size, coalescing wants them ordered by address. Maintaining both costs bookkeeping on every insertion, and that cost buys fragmentation resistance.

Result

The allocator services allocation and deallocation requests against its own heap, reuses freed memory through the binned free list, and merges adjacent free blocks to limit fragmentation. The source is not published.

What I learned

Allocator design is a study in policy rather than mechanism. Splitting, coalescing, placement, and binning are each individually simple; the difficulty is that they interact, and a choice that looks locally cheap can change the shape of the heap over a long run. That framing carried directly into how I think about caches and other systems where reuse policy determines behavior.

Contact information

Based in Austin, Texas

Let’s talk.

Email works best, but feel free to reach out anywhere.

Phone
(469) 465-1537
Primary email
[email protected]
University email
[email protected]
Profiles