Small C++17 examples of arena-style memory backed by mmap(2): a fixed-block pool allocator (PoolMemory) and a bump / linear allocator (LinearMemory). The CMake project is named mymalloc; the executable links main.cpp with the allocator implementations.
- CMake 3.25 or newer
- C++17 compiler (Clang or GCC)
- POSIX environment with
mmap/munmap(macOS, Linux, and similar). This code is not portable to Windows without replacing the mapping layer.
cmake -S . -B build
cmake --build buildRun the demo:
./build/mymalloc| Path | Role |
|---|---|
include/pool_allocator.h, src/pool_allocator.cpp |
Fixed-size block pool |
include/linear_allocator.h, src/linear_allocator.cpp |
Linear bump allocator |
include/utils.h |
Alignment helper |
main.cpp |
Example using PoolMemory |
Idea: Reserve a contiguous region, slice it into equal-sized blocks, and keep free blocks on a singly linked free list. Allocation pops a node from the list and placement-news your object there.
static PoolMemory *init(size_t blocks = 1024 * 1024, uint8_t size_per_block = 64);blocks— number of blocks in the pool.size_per_block— minimum payload bytes per block; internally clamped to at leastsizeof(FreeNode)and rounded up to 8-byte alignment.
The backing store is anonymous private memory from mmap.
template <typename Entity, typename... Args>
Entity *assign(Args... args);Constructs Entity in the next free block with Entity(args...). If sizeof(Entity) exceeds the block size, the program prints a message and calls exit(1).
template <typename T>
void free(T *ptr);Returns the block to the free list after delete ptr (so T must have a trivial or appropriate destructor path for how you use the pool). The pointer must have come from assign on this pool.
print_stats()— prints allocator-related diagnostics (implementation-specific).
Idea: One contiguous arena: a cursor advances on each assign, with alignment satisfied for each T before constructing the object. There is no per-object free; you reset or unmap the whole region.
static LinearMemory *init(std::size_t mem_size,
std::size_t alignment = sizeof(std::max_align_t));- Reserves enough anonymous mapped memory for the
LinearMemoryheader plusmem_sizebytes of user arena, rounded up to a whole number of pages (usingsysconf(_SC_PAGE_SIZE)). - Logs requested size, total need, page size, and aligned mapping size to
stdout.
template <typename T, typename... Args>
T *assign(Args... args);Aligns current to alignof(T), bumps the cursor by sizeof(T), placement-news T(args...). If the aligned object would cross soft_end (end of the user arena), prints an error (including “wasted” slack to hard_end) and exit(1).
bool free(); // munmap entire mapping; returns true if munmap succeededThe destructor calls free().
print_stats()— prints addresses, sizes, and slack between soft and hard end.
static size_t alignment(size_t need, size_t multiple);Returns the smallest value >= need that is a multiple of power-of-two multiple (typical use: page size or alignments).
PoolMemory *pool = PoolMemory::init(100);
Entity *entity = pool->assign<Entity>(1, 2, 4);
entity->printEntity();This creates a pool of 100 blocks (default 64-byte blocks unless you pass a second argument), allocates one Entity, and prints it.
- Pool: Good for many same-sized or small objects; constant-time allocation from the free list when blocks fit.
- Linear: Very fast sequential allocation and simple teardown; no individual frees—patterns like “frame allocator” or “parse this buffer in one pass” fit well.
For production use you would typically add error handling instead of exit, optional munmap in PoolMemory’s destructor, and tests that cover alignment edge cases and exhaustion.