Part of the Purpose-Built References (PBR) series — small, didactic, production-quality C/C++ reference implementations of high-performance building blocks.
Read this in: 简体中文 · 日本語 — English is the normative source (ADR-0032).
Many high-performance systems — graphics engines, financial trading servers, databases — suffer from memory fragmentation and the overhead of frequent malloc/free (or new/delete) calls. This component provides a custom Memory Pool that pre-allocates a contiguous block of memory and delivers constant-time, fixed-size allocation with zero external fragmentation. It is a single, focused C/C++ library — a header-backed static library with a four-function C ABI and an idiomatic C++17 wrapper — built to enterprise quality (warnings-as-errors, sanitizers, Valgrind, Doxygen) and documented decision-by-decision in its ADRs.
- Allocation: O(1), fixed block size, contiguous backing storage.
- Free-list strategy: implicit — free blocks store the next-free pointer in their own first
sizeof(void*)bytes, so live blocks carry zero metadata overhead. - Metadata overhead: 0 bytes per block (the free-list link reuses unused block storage) + a fixed ~40 bytes per pool — independent of
block_count. CI-gated at ≤ 128 bytes per ADR-0015. - Standards: ANSI C public surface, C++17 internals and wrapper. No external dependencies.
- Thread safety: opt-in, configurable at compile time (Milestone 4).
- Dynamic growth: optional contiguous overflow chunks (Milestone 5).
- Observability: opt-in
InstrumentedPoolDecorator (stats, occupancy) +PoolObserverfor lifecycle events — zero cost to undecorated pools (Milestone 6). - Quality gates:
clang-tidyclean, ASan + UBSan + (when threading lands) TSan green, Valgrind clean, Doxygen-documented public surface. - Benchmark target: measured against
malloc/freeover 1,000,000 iterations.
The complete public surface is four functions. The full contract is in the spec:
typedef struct memory_pool memory_pool_t;
memory_pool_t* memory_pool_create(size_t block_size, size_t block_count);
void* memory_pool_alloc(memory_pool_t* pool);
void memory_pool_free(memory_pool_t* pool, void* block);
void memory_pool_destroy(memory_pool_t* pool);A C++17 RAII wrapper (it::d4np::memorypool::Pool) and a typed template (TypedPool<T>) layer on top of this surface — see Milestones 2.5 and 3.2 in ROADMAP.md.
The complete, cross-linked API reference — every public symbol's parameter / return / throws contract — is generated from the in-header Doxygen comments and published to GitHub Pages on every push to master (ADR-0027). The same build runs as a warn-as-error gate on every PR.
The public include root is src/main/cpp, so headers are included as <it/d4np/memorypool/…>; link against the static library target pbr_memory_pool (see Build and test). Every snippet below is compiled and run as-is by the maintainer before each release. The C++ surface lives in namespace it::d4np::memorypool (elided below for brevity).
#include <it/d4np/memorypool/memory_pool.h>
memory_pool_t* pool = memory_pool_create(64, 1024); /* 1024 blocks of 64 bytes */
if (pool != NULL) {
void* block = memory_pool_alloc(pool); /* O(1); NULL when exhausted */
if (block != NULL) {
memory_pool_free(pool, block); /* O(1); back to the free list */
}
memory_pool_destroy(pool); /* releases all backing storage */
}Pool owns the handle (constructed → created, destroyed → destroyed) and is move-only. The exception policy is dual-verb (ADR-0016): allocate() throws std::bad_alloc on exhaustion, try_allocate() is noexcept and returns nullptr.
#include <it/d4np/memorypool/memory_pool.hpp>
using namespace it::d4np::memorypool;
Pool pool(64, 1024); // throws std::bad_alloc on bad config
if (void* block = pool.try_allocate()) { // non-throwing verb
pool.deallocate(block);
}
// Failure as a value instead of an exception — Factory Method / Builder (ADR-0011):
if (std::optional<Pool> p = Pool::make(64, 1024)) {
void* b = p->allocate(); // throwing verb — never nullptr
p->deallocate(b);
}
auto built = PoolBuilder{}.with_block_size(64).with_block_count(1024).build();Derives a spec-conformant block_size from T at compile time and adds object-lifetime verbs (construct placement-news, destroy runs the destructor).
#include <it/d4np/memorypool/typed_pool.hpp>
TypedPool<Widget> pool(1024);
Widget* w = pool.construct(arg1, arg2); // allocate + placement-new (strong guarantee)
// ... use w ...
pool.destroy(w); // ~Widget() + return the slot to the poolA Cpp17Allocator Adapter (ADR-0018). Node-based containers (std::list, std::map, std::set) run on the O(1) pool fast path; oversized / multi-element requests transparently fall back to ::operator new. The pool must out-live the container.
#include <it/d4np/memorypool/pool_allocator.hpp>
#include <list>
Pool pool(64, 1024); // 64-byte blocks fit a list<int> node
std::list<int, PoolAllocator<int>> values{PoolAllocator<int>{pool}};
for (int i = 0; i < 100; ++i) {
values.push_back(i); // each node served from the pool
}Opt-in, per-pool (ADR-0022): on exhaustion the pool acquires a new contiguous chunk and grows geometrically instead of failing. Default pools stay fixed-size.
if (std::optional<Pool> pool = Pool::make_dynamic(64, 256, /*growth_factor=*/2)) {
for (int i = 0; i < 100000; ++i) {
(void)pool->try_allocate(); // grows 256 → 512 → … instead of returning nullptr
}
}An opt-in Decorator (ADR-0025) that counts allocation activity and emits lifecycle events (ADR-0026). A program that uses Pool directly pays nothing.
#include <it/d4np/memorypool/instrumented_pool.hpp>
struct LoggingObserver : PoolObserver {
void on_pool_event(PoolEvent event, const PoolStats& stats) noexcept override {
if (event == PoolEvent::exhausted) {
std::cerr << "pool exhausted at " << stats.live_ << " live blocks\n";
}
}
};
if (auto pool = InstrumentedPool::make(64, 1024)) {
LoggingObserver observer;
pool->add_observer(observer); // observer must out-live the pool
void* b = pool->try_allocate();
pool->deallocate(b);
PoolStats s = pool->stats(); // allocations_, deallocations_, live_, peak_live_, …
pool->write_summary(std::cout);
}The pool manages free memory using a free list embedded inside the free blocks themselves: when a block is free, its first sizeof(void*) bytes hold the address of the next free block. Live blocks carry no metadata at all.
+-------------------------------------------------------------------+
| Memory Pool |
+-------------------------------------------------------------------+
| [Block 1 (free)] -> next-free ptr to Block 2 |
| [Block 2 (in use)] -> user data |
| [Block 3 (free)] -> next-free ptr to Block 4 |
| [Block 4 (free)] -> NULL (end of free list) |
+-------------------------------------------------------------------+
The free-list layout, the block_size ≥ sizeof(void*) constraint, and the alignment guarantees are locked down in ADR-0009; the surrounding decisions are recorded in ADR-0002 and ADR-0003.
Summary. Against malloc/free, a pre-sized fixed pool is 4–11× faster single-threaded; a growing pool stays ~2× faster even while it acquires chunks; under thread contention the lock-free policy beats the mutex but a single-shared-head pool does not out-scale malloc's per-thread arenas. All numbers below are on the maintainer's Intel i5-6600K (Skylake) × Windows 10 × MSVC 19.51 Release workstation, 64-byte blocks, 1,000,000 iterations × 10 repeats (first dropped as warm-up). Methodology contract: ADR-0014.
Fixed, single-threaded (M2.9 / spec §6.3 — full report: docs/bench/v0.2.0-windows-msvc-x64.md):
| Scenario | median malloc (ns/op) |
median pool (ns/op) |
malloc / pool |
|---|---|---|---|
| bulk-alloc | 75.5 | 6.9 | 11.02 × |
| bulk-free | 44.5 | 8.3 | 5.35 × |
| interleaved | 49.9 | 11.2 | 4.45 × |
Dynamic growth (M5.4 — full report: docs/bench/v0.5.0-windows-msvc-x64-growth.md): a pool that grows from 256 blocks to 1,000,000 during the run does amortized bulk-alloc at 55 ns/op vs malloc's 108 — 1.96× faster, the cost of ~12 geometric chunk acquisitions. Size up front when the working set is known (the ~11× path); use growth when it is not.
Threading (M4.5 — full report: docs/bench/v0.4.0-windows-msvc-x64-threading.md): --scenario concurrent runs T threads against a shared pool, built per thread-safety policy. The single-thread fast path is preserved (NONE ≈ 9 ns/op interleaved, unchanged); under 4-thread contention LOCKFREE (41.8 ns/op) beats MUTEX (69.5 ns/op).
The bench binary is built off by default; the bench preset (Release + benchmarks ON + tests OFF) opts in:
cmake --preset bench
cmake --build --preset bench
./build/bench/src/bench/cpp/it/d4np/memorypool/pool_vs_malloc_benchReports for other host × compiler combinations (Linux / GCC, Linux / Clang, macOS / Apple Clang) are welcome — see docs/bench/README.md for the contribution recipe.
v1.1.2 — maintenance (bug fixes + documentation), a PATCH over v1.1.1. The public surface is unchanged — no API/ABI change. Fixes four verified, externally-reported defects (the first use of the in-repo bug ledger, ADR-0039): an InstrumentedPool growth-counter data race (BUG-0001), a live_ counter underflow on foreign/double frees (BUG-0002), a missing destroyed event on move-assignment (BUG-0003), and a latent grow_pool overflow guard (BUG-0004). Also removes the redundant docs-site README badge and re-syncs the zh-Hans/ja translations. Release notes: docs/releases/v1.1.2.md. The earlier line:
v1.1.1 — maintenance (documentation / process / tooling), the first post-v1.1.0 PATCH. The shipped library is byte-identical to v1.1.0 — no API/ABI/behaviour change. Adds the in-repo bug ledger + triage protocol (ADR-0039), a PR-metadata policy, a SECURITY.md, packaging-smoke CI for the vcpkg/Conan recipes, the session journal (ADR-0036), the new-feature roadmap-placement rule (ADR-0037), and the per-release changelog split (ADR-0038). Five new ADRs (0036–0040) bring the total to 40. Release notes: docs/releases/v1.1.1.md. The earlier line:
v1.1.0 — internationalization & post-release governance (Milestone 8), the first post-1.0 MINOR. Purely additive — the library binary is unchanged from v1.0.x. Documentation now ships in Simplified Chinese (zh-Hans) and Japanese (ja) (English stays normative — docs/i18n/, ADR-0032); the spec is English-normative (ADR-0033); a post-release maintenance protocol (ADR-0034) governs the maintained-product phase; and an agent-runnable consistency lint (ADR-0035) gates cross-artifact congruence in CI and the agent contract. Four new ADRs (0032–0035) bring the total to 35. Release notes: docs/releases/v1.1.0.md. The earlier line:
v1.0.1 — packaging patch over the frozen v1.0.0 API: adds the vcpkg port and the Conan 2.x recipe (Phase 2 distribution, ADRs 0030 / 0031). The shipped library is byte-identical to v1.0.0 — a PATCH, no API/ABI/behaviour change. Release notes: docs/releases/v1.0.1.md. The stable baseline it patches:
v1.0.0 — the first stable release. The public C ABI (memory_pool_create / _alloc / _free / _destroy plus the O(1) introspection accessors) and the C++ surface (Pool, TypedPool<T>, PoolAllocator<T>, InstrumentedPool, PoolObserver) are frozen under the SemVer 1.0 promise — no breaking change without a 2.0.0. v1.0.0 seals the feature set built across Milestones 0–6 — the O(1) implicit-free-list fixed-block pool with zero per-block metadata, the RAII / typed / STL-allocator C++ wrappers, compile-time-configurable thread safety, optional geometric dynamic growth, and opt-in observability — and adds the Milestone 7 polish: the published Doxygen API site (M7.1), the expanded usage / performance / compatibility README (M7.2), find_package install + pkg-config packaging (M7.4), and the patterns-catalogue (M7.5) and specification-compliance (M7.6, ADR-0029) acceptance audits. All fifteen Spec Coverage Map rows are ✅, re-verified end-to-end. Twenty-nine ADRs (0001–0029) record every decision; all eleven adopted design patterns are Implemented. Release notes for v1.0.0 live in docs/releases/v1.0.0.md.
| Milestone | Title | Status |
|---|---|---|
| 0 | Agent & Workflow Scaffolding | ✅ complete |
| 1 | Build System & Project Skeleton | ✅ complete |
| 2 | Core Memory Pool (single-threaded) | ✅ complete |
| 3 | C++ Wrapper & Type Safety | ✅ complete |
| 4 | Thread-Safe Variant | ✅ complete |
| 5 | Dynamic Growth Mode | ✅ complete |
| 6 | Observability & Decorators | ✅ complete |
| 7 | Release & Polish | ✅ complete |
| 8 | i18n & Post-Release Governance | ✅ complete |
See ROADMAP.md for the per-task breakdown and the Spec Coverage Map at the bottom (traceability from spec sections to roadmap items).
The library targets C++17 (strict, no compiler extensions) for the implementation and ANSI C (C89) + C99 for the public C header — both verified in CI. There are no external dependencies: only the C and C++ standard libraries. The full rationale and floor-version reasoning are in ADR-0005.
Tier-1 — gated on every PR (Linux × {GCC, Clang} + Windows × MSVC + macOS × Apple Clang, across Debug / Release and, where applicable, ASan / UBSan / TSan):
| OS | Arch | Compilers (minimum) |
|---|---|---|
| Linux | x86_64 | GCC ≥ 11, Clang ≥ 14 |
| Windows | x86_64 | MSVC ≥ 19.30 (VS 2022 17.0); clang-cl ≥ 14 optional |
| macOS | arm64 | Apple Clang ≥ 14 (Xcode 14) |
Tier-2 — best-effort, not gated: Linux aarch64, macOS x86_64 (pre-Apple-Silicon), FreeBSD x86_64, MinGW-w64 on Windows.
Language standards:
| Surface | Standard | Build flags |
|---|---|---|
| C++ implementation | C++17 (strict) | -std=c++17 / /std:c++17, -Wall -Wextra -Wpedantic -Werror / /W4 /WX |
| C public header | ANSI C (C89) and C99 | dedicated CI jobs: -std=c89 -pedantic -Werror, -std=c99 -pedantic -Werror |
Thread safety is selected at build time via the PBR_MEMORY_POOL_THREAD_SAFETY CMake option — NONE (default, single-thread fast path), MUTEX, or LOCKFREE (ADR-0020). Dynamic growth is supported under NONE and MUTEX (not LOCKFREE — ADR-0024 §2).
The library has zero runtime/build dependencies beyond the C and C++ standard libraries (spec §3.3); everything below is either a language standard, a build/test/docs tool, or a packaging integration. The consumer-facing compiler & platform matrix is in Compatibility above.
| Layer | Technology | Version |
|---|---|---|
| Language (impl) | C++ | C++17 (strict, no extensions) |
| Language (C public header) | ANSI C | C89 and C99 |
| Build system | CMake | ≥ 3.21 |
| Build generator | Ninja (CI & presets) | any recent |
| Compilers (floor) | GCC / Clang / MSVC / Apple Clang | 11 / 14 / 19.30 (VS 2022 17.0) / 14 (ADR-0005) |
| Unit tests | doctest (via CMake FetchContent, test-only) |
v2.4.11 |
| Runtime sanitizers | ASan · UBSan · TSan | compiler-bundled |
| Memory checker | Valgrind | distro (CI: Ubuntu 24.04) |
| Static analysis / style | clang-tidy · clang-format | LLVM 14+ |
| API docs | Doxygen → GitHub Pages | 1.10.x (ADR-0027) |
| Project tooling | Python (consistency lint, stdlib-only) | 3.x (ADR-0035) |
| Packaging | CMake find_package + pkg-config · vcpkg port · Conan recipe |
ADR-0028 / 0030 / 0031 |
| CI | GitHub Actions | — |
| Runtime dependencies | none | — |
Every PR must clear, at minimum:
| Gate | Requirement |
|---|---|
| Compiler matrix | MSVC, GCC, Clang — Debug & Release builds |
| Warnings | -Wall -Wextra -Wpedantic -Werror (GCC/Clang) or /W4 /WX (MSVC) — zero |
clang-tidy |
Clean on the diff; no broad disables |
| Unit tests | Cover new/changed behaviour; pass on every compiler |
| Sanitizers | ASan + UBSan green; TSan once threading is touched |
| Valgrind | ERROR SUMMARY: 0 errors from 0 contexts on the demonstrative test |
| Public API docs | Doxygen-compatible, builds without warnings |
| Performance claims | Backed by reproducible benchmark under src/bench/ |
Full quality contract: AGENTS.md §10. The C++ build matrix, sanitizers, clang-format, clang-tidy diff gate, ANSI C / C99 verification, and zero-external-dependency audit run on every PR via ci.yml; a docs-only workflow (docs.yml) covers markdownlint, internal link checks, and ADR numbering sanity; a docs-site workflow (docs-site.yml) builds the Doxygen API reference as a warn-as-error gate on every PR and publishes it to GitHub Pages on push to master (ADR-0027). The badges above gate master.
The canonical three-step workflow, once the toolchain is installed, is the same on every supported platform — only the shell-level chaining differs.
# Linux, macOS, MinGW/MSYS2, WSL — any POSIX shell
cmake --preset debug
cmake --build --preset debug
ctest --preset debug# Windows — Developer PowerShell for VS 2022 (PowerShell 5.1 has no &&)
cmake --preset debug
cmake --build --preset debug
ctest --preset debugBoth invocations are exercised end-to-end on every push to master by the CI matrix: Linux × {GCC, Clang}, Windows × MSVC, macOS × Apple Clang — across debug, release, asan, and ubsan presets (sanitizer presets are POSIX-only per ADR-0005 §3). A green ci badge above is the canonical "the quickstart works on Windows and Linux" signal.
For first-time setup on a fresh clone — installing CMake, Ninja, and the supported compilers per platform, plus troubleshooting and the full quality-bar workflow — see the Local Build Guide.
Install to a prefix and consume with CMake's find_package (ADR-0028 — Phase 1 distribution):
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DPBR_MEMORY_POOL_BUILD_TESTS=OFF
cmake --build build
cmake --install build --prefix /your/prefix# In the consumer's CMakeLists.txt — the imported target name is identical
# whether you install the package or vendor it via add_subdirectory / FetchContent.
find_package(pbr_memory_pool CONFIG REQUIRED)
target_link_libraries(my_app PRIVATE pbr::memory_pool)The install tree carries every public header (include/it/d4np/memorypool/), the static archive, the CMake package config (SameMajorVersion compatibility), and a pkg-config pbr-memory-pool.pc for non-CMake (Make / autotools / Meson) builds. The same cmake --install tree is what each GitHub Release tarball contains. Vendoring without installing also works:
add_subdirectory(path/to/pbr-cpp-memory-pool) # or FetchContent
target_link_libraries(my_app PRIVATE pbr::memory_pool)vcpkg (Phase 2 — ADR-0030): a port pinned to v1.0.0 ships in ports/, consumable today as an overlay port (the same pbr::memory_pool target):
vcpkg install pbr-memory-pool --overlay-ports=portsConan (Phase 2 — ADR-0031): a Conan 2.x recipe pinned to v1.0.0 ships in conan/, creatable today (same pbr::memory_pool target via CMakeDeps):
conan create conan/ # then depend on pbr-memory-pool/1.0.0Registry publication (microsoft/vcpkg, ConanCenter / self-hosted) is deferred — see ports/README.md and conan/README.md.
| Path | What lives there |
|---|---|
AGENTS.md |
Cross-tool contract for AI coding agents (Codex, Claude, Gemini). |
CLAUDE.md |
Claude Code adapter — defers to AGENTS.md. |
GEMINI.md |
Gemini Antigravity adapter — defers to AGENTS.md. |
ROADMAP.md |
Numbered checkbox plan + Spec Coverage Map. |
CHANGELOG.md |
Keep a Changelog 1.1.0 history; user-visible changes per release (see ADR-0004). |
src/ |
All source code, Maven-style layout — src/{main,test,bench}/cpp/it/d4np/memorypool/. |
docs/specs/ |
Functional and technical specifications. |
docs/adr/ |
Architecture Decision Records. |
docs/patterns/ |
Design-patterns catalogue + canonical enterprise taxonomy. |
docs/workflow/ |
Git and documentation conventions. |
docs/development/ |
Procedural how-to guides for local development (build, debug, profile). |
docs/i18n/ |
Documentation translations (zh-Hans, ja); English is normative (ADR-0032). |
Start by reading, in this order:
docs/specs/01_spec_cpp_memory_pool.md— what we are building.docs/development/local-build.md— how to build and test it on your machine.docs/adr/— why the project is structured this way.docs/patterns/README.md— which design patterns we exercise and why.docs/workflow/git-workflow.md— branch, commit, and PR conventions.ROADMAP.md— what is done and what is next.
This repository is configured to work with Claude Code, Gemini Antigravity, and ChatGPT Codex. The agent contract lives in AGENTS.md — read it before doing anything. Short version:
- Every artifact (code, docs, commits, branches, PRs) is in English.
- You commit, push, and draft pull requests on feature branches. The maintainer opens and merges PRs manually.
- Non-trivial design decisions are recorded as ADRs in the same PR.
- Every adopted design pattern is justified in an ADR and listed in
docs/patterns/README.md. - Enterprise quality bar: warnings-as-errors,
clang-tidyclean, ASan/UBSan/TSan green, Valgrind clean, Doxygen documented. README.mdandROADMAP.mdare kept current in the same PR as the work they describe.
MIT © 2026 Daniel Polo.