ClientApp
Owns test heaps, exercises the public API, runs named scenarios, and writes validated snapshots to JSON.
Implementation notes / C++ and Win32
A C++ allocator that reserves Windows virtual memory, manages blocks with intrusive free lists and boundary tags, and caches frequently requested small sizes.
Real native snapshots. No browser simulation.
No payload contents. No process addresses.
01 / The problem
Windows gives a process virtual memory in pages. Applications usually need much smaller, differently sized allocations. The problem here is to divide one reserved region into usable blocks, remember which bytes are free, reuse them safely, and keep existing payload addresses stable when committing more memory.
This is an implementation exercise, not an attempt to replace the CRT or Windows heap. The useful outcome is understanding the metadata, ownership rules, and tradeoffs behind the familiar allocation API.
my_malloc(heap, size)The same region backs both the general free list and the small-block buckets. A bucket is not another OS heap.
02 / Architecture
Owns test heaps, exercises the public API, runs named scenarios, and writes validated snapshots to JSON.
A DLL containing the first-fit backend, split/coalesce logic, 51 size classes, heap lock, counters, and snapshot API.
Defines the embedded NODE and reusable list operations. HeapManager uses the layout but manages its own lists under its heap lock.
A static logging library with source context and synchronized output. It is diagnostic support, not an interactive debugger.
The client calls the DLL; the DLL calls VirtualAlloc and VirtualFree. Snapshot records travel in the other direction: native state → caller-owned records → JSON → this static page. The browser cannot invoke the DLL or change its heap.
03 / Allocation lifecycle
my_heap_create reserves at least one 64 KiB chunk, commits the first chunk, stores the HEAP metadata at the front, and places the remaining committed span in one backend free block. Destruction releases the entire region; callers must first stop every heap user.
Free-list neighbors are not necessarily memory neighbors. Boundary tags locate physical neighbors; intrusive links only maintain list membership.
04 / Memory layout
The header stores a packed total size and state flags, plus the exact requested length. A free block reuses its idle payload for list links and a footer. Cached blocks keep the physical used flag and add a cached flag, preventing the backend from coalescing through them.
| Build | Alignment | Aligned header | Minimum block |
|---|---|---|---|
| x64 Release | 16 | 16 | 48 |
| x64 Debug | 16 | 32 | 64 |
| Win32 Release | 8 | 8 | 24 |
| Win32 Debug | 8 | 16 | 32 |
Debug adds magic/allocation-ID fields and an eight-byte guard immediately after the requested payload. Class rounding can make an allocation larger than the minimum block. The flags are MH_USED = 1, MH_PREV_USED = 2, and MH_CACHED = 4; masking alignment bits recovers the total size.
05 / LFH-style buckets
The frontend groups total block sizes into 51 classes, from 32 bytes through 16 KiB. A class activates after its request counter reaches 16. Freed blocks can then be parked and reused without a first-fit search. This is a custom LFH-style cache, not the Windows LFH API.
Occupancy is the number of parked blocks. Demand is the recent request count. Recency is when the class was last pushed or popped. They are separate fields because a cache can be full but no longer useful.
A warm drain retains cap / 8 blocks. A cold retirement can empty the class completely. Cold age uses 4,096 heap-operation ticks, not wall-clock seconds. Periodic cleanup is checked at allocation entry; there is no background sweeper.
A demand reset can lower the cap below existing occupancy. The lower cap restricts new pushes; it does not instantly evict every excess block.
Formula illustration / not a native snapshot
cap = clamp(4 + demand, 4, 256)keep = floor(cap / 8)
This calculator changes neither the captured heap nor any native allocator setting. The scale represents a maximum of 256 cached blocks, not bytes.
06 / Technical decisions
| Decision | Why it helps | What it costs or does not solve |
|---|---|---|
| Intrusive free-list nodes | No separate allocation to record each free block; known nodes unlink cheaply. | Does not index blocks by size. First-fit still scans the list. |
| Split and coalesce | A useful remainder is reusable; adjacent backend-free blocks become one range. | Live blocks can still separate free space. Rounding and unsplittable tails still waste bytes. |
| Demand-driven buckets | Repeated sizes can reuse a parked block without searching. | Retains committed bytes and delays coalescing until reclamation. |
| One critical section per heap | Keeps multi-step metadata updates simple and consistent. | Contended metadata operations serialize; no claim of linear thread scaling. |
| Physical-boundary validation | Rejects bad payload boundaries before trusting a caller's pointer. | Free/realloc lookup is O(B); complete list validation can be O(B²), for B physical blocks. |
Coalescing reduces external fragmentation; it does not eliminate it. Caching improves a reuse path; it does not guarantee a faster allocator. These distinctions matter more than presenting a favorable score.
07 / Debugging and validation
The validator is diagnostic support, not a security boundary against arbitrary metadata writes. Raw pointers also have a reuse limitation: an old alias can look valid after its address is assigned to a new live allocation. The API still requires live heap handles and correct ownership.
08 / Test scenarios and evidence
| Scenario | What the check expects |
|---|---|
split-merge | Three large allocations eventually coalesce into one backend-free block. |
bucket-reuse | A hot class parks one block; the next request reuses its offset. |
changing-workload | Warm reclamation reduces 24 cached blocks to 3 without growth, then cold retirement empties the cache. |
growth | A 96,000-byte request increases commitment; freeing does not decommit pages. |
concurrent | Four workers hold 16 blocks each at a barrier, then verify and free their payloads. |
The original eight-thread workload remains. Additional regressions cover alignment, overflow, realloc failure, invalid pointers, repeated frees, snapshot capacity, controlled corruption, list operations, and logger lifecycle. Local verification has passed on x64 and Win32 in Debug and Release. The repository includes Windows CI; a hosted CI result must be checked separately after publishing.

09 / Supporting visualization
Loading the bundled native trace…
Local files stay in your browser · 8 MiB maximum · Structure is validated, not producer identity. Bundled loading requires HTTP; local selection also works when opening this HTML directly.
Awaiting capture
A reservation is address space, not resident RAM. The hatched area has not been committed.
Widths are proportional. Focus filters dim other ranges without rescaling memory or changing counters.
Between recorded frames
Compared with the preceding frame in this scenario, even when you jump backward. Range changes are observations—not allocation identities or a log of every operation between captures.
Class sizes include allocator overhead. An active bucket can be empty.
| Block | Offset | Total bytes | Requested bytes | State | Bucket |
|---|
10 / Reproduce the behavior
HeapManager/HeapManager.slnx in Visual Studio with the C++ workload. Build ClientApp.bin\x64\Debug\ClientApp.exe --test
bin\x64\Debug\ClientApp.exe --export trace.json
Open the exported file in the explorer above. For GitHub Pages, publish this repository's root from your chosen branch. The page needs no server-side code, account, or frontend build step.
README · Heap design · Snapshot contract and internals · Complexity and tradeoffs
11 / Benchmark plan
No comparative allocator benchmark has been run for this page. Correctness tests and snapshot playback are not performance measurements, and no speedup is claimed. The following is the planned experiment—not a results table.
| Allocator | API family | Status |
|---|---|---|
| This heap manager | my_malloc / my_free / my_realloc | Pending measurement |
| C runtime | malloc / free / realloc | Pending measurement |
| Windows heap | HeapAlloc / HeapFree / HeapReAlloc | Pending measurement |
12 / Limitations and future work
13 / What I learned
These are the technical takeaways visible in this implementation, rather than claims about performance or production readiness.
The goal is a design that can be explained, reproduced, and questioned—not a claim that a small custom allocator is better than a mature standard one.