Memory Pool Arena Allocator
Guided Challenges and Capstone

10.2 Memory Pool Arena Allocator

General-purpose new handles arbitrary sizes and lifetimes. Arenas trade flexibility for predictable bump allocation: advance an offset through a pre-allocated buffer in O(1) time, then free everything at once. Lesson 7.5 introduced std::pmr; this exercise implements the idea manually so you see the pointer arithmetic.

When an arena helps

* Many small objects share one lifetime (a frame, a request, a level load).

* You can batch-deallocate instead of calling delete per object.

* Allocation hot paths must avoid allocator locks, measure first; modern allocators are often fast for small sizes.

Bump pointer mechanics

Keep a char* buffer, capacity, and offset. Each allocation aligns the current offset, returns buffer + offset, then advances offset by the requested size. reset() sets offset = 0, no per-object free list.

Alignment padding formula

Given current address A and required alignment align, padding is (align - (A % align)) % align. Lesson 3.5 explained why alignment matters for SIMD and cache lines; the arena must honor alignof(T) for placement new.

Placement new after allocate

The arena returns raw bytes; placement new constructs the object in that storage. There is no matching delete, call destructors manually if types are non-trivial before reset(), or restrict this exercise to trivially destructible Particle objects as documented.

void* mem = arena.allocate(sizeof(Particle), alignof(Particle));
Particle* p = new (mem) Particle{1.0f, 2.0f, 3.0f};
// p->~Particle(); // required before reset() for non-trivial types
Placement new into arena memory (conceptual).

Limitations of this minimal arena

No individual deallocate, no thread safety, no fallback when the buffer is exhausted except throwing. Production arenas often chain blocks or fall back to an upstream resource, std::pmr::monotonic_buffer_resource does exactly that.

The Challenge: Bumping Offsets & Placement New

Your Arena class must:

1. Pre-allocate a fixed heap buffer in the constructor.

2. Implement allocate(size, alignment) with padding, bounds check, and offset advance.

3. Release the buffer in the destructor; support reset() for bulk reuse.

Fill in allocate in the playground template below.

#include <iostream>
#include <cassert>
#include <new>
#include <cstddef>
#include <stdexcept>

class Arena {
private:
    char* buffer;
    size_t capacity;
    size_t offset;
public:
    Arena(size_t cap) : capacity{cap}, offset{0} {
        buffer = new char[cap];
    }

    ~Arena() {
        delete[] buffer;
    }

    Arena(const Arena&) = delete;
    Arena& operator=(const Arena&) = delete;

    // TODO: Implement aligned bump allocation (padding, bounds check, advance offset)
    void* allocate(size_t size, size_t alignment = alignof(std::max_align_t)) {
        // Your code here
        return nullptr;
    }

    void reset() {
        offset = 0;
    }

    size_t getUsedMemory() const { return offset; }
};

struct Particle {
    float x, y, z;
    Particle(float px, float py, float pz) : x{px}, y{py}, z{pz} {}
};

int main() {
    Arena arena{1024};

    void* mem1 = arena.allocate(sizeof(Particle), alignof(Particle));
    Particle* p1 = new (mem1) Particle{1.0f, 2.0f, 3.0f};
    assert(p1->x == 1.0f && p1->y == 2.0f && p1->z == 3.0f);
    assert(arena.getUsedMemory() == sizeof(Particle));

    void* mem2 = arena.allocate(sizeof(Particle), alignof(Particle));
    Particle* p2 = new (mem2) Particle{4.0f, 5.0f, 6.0f};
    assert(p2->x == 4.0f);
    assert(arena.getUsedMemory() == sizeof(Particle) * 2);

    arena.reset();
    assert(arena.getUsedMemory() == 0);

    std::cout << "All Arena Allocator assertions passed successfully!\n";
    return 0;
}
Arena Allocator template capstone challenge code.

If you get stuck, expand the reference solution below.

Reference Solution: Arena Allocator pointer arithmetic
Key solution features:
// - Offset math aligns the starting address based on structural alignment requirements
// - Advances offset position to serve subsequent allocations in O(1) speed
// - Deletes buffer pointer array inside destructor to prevent memory leaks

void* allocate(size_t size, size_t alignment) {
    size_t currentAddress = reinterpret_cast<size_t>(buffer + offset);
    size_t padding = (alignment - (currentAddress % alignment)) % alignment;
    if (offset + padding + size > capacity) {
        throw std::bad_alloc();
    }
    offset += padding;
    void* address = buffer + offset;
    offset += size;
    return address;
}
Reference Solution: Arena Allocator pointer arithmetic.
Finished reading this lesson?