Skip to content

xtellect/spaces

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Spaces

License: MIT

Spaces is a single-file C allocator for Linux x86-64. It works as a drop-in malloc replacement, but its distinctive feature is that it also gives you explicit heap regions: create a heap for a subsystem, cap its memory, inspect every live allocation, share it across processes, and destroy the entire region in one call.

Use cases:

  • Parser/compiler arenas — free an entire million-node AST with one spaces_chunk_destroy() call instead of chasing pointers.
  • Memory-bounded caches — set a hard ceiling on a chunk and let the allocator enforce it, so your server doesn't OOM at 3 AM.
  • Runtime diagnostics — walk every live allocation in a heap region without an external profiler, recompilation, or debug build.

Quick start

gcc -O3 -pthread -fPIC -c spaces.c
ar rc libspaces.a spaces.o
gcc your_app.c -Wl,--whole-archive libspaces.a -Wl,--no-whole-archive -lpthread
#include "spaces.h"

// A bounded cache with enforced ceiling
SpacesChunk cache = spaces_chunk_create(0);
spaces_chunk_set_ceiling(cache, 256 * 1024 * 1024);

void *p = spaces_chunk_alloc(cache, request_size, 0);
if (!p) evict_oldest();                 // ceiling enforced by the allocator

spaces_chunk_destroy(cache);            // instant teardown, everything freed
// Or just use it as malloc — same binary, no code changes
void *p = malloc(4096);
free(p);

Why I built this

I maintain a codebase where different subsystems need their own memory budgets, and where cleanup is the hardest part: a parser allocates a tree, the tree gets transformed, intermediate structures are discarded, and the final output lives in a separate region. With a standard allocator you either track and free every node, or you leak.

I wanted an allocator where I could say: "this heap belongs to the parser; when parsing is done, destroy it." And I wanted the allocator itself to be fast enough that I wouldn't pay a tax for the organisational benefit.

Existing options fell into two camps. The fast allocators (jemalloc, tcmalloc, mimalloc) are black boxes — fast malloc/free, nothing else. The region allocators (Apache APR pools, Loki's small object allocator) give you structure but aren't competitive as general malloc replacements. Spaces tries to be both: a fast malloc with the internal structure to support regions, introspection, budgets, shared memory, and per-subsystem tuning.

The trade-off I chose: every free() reads one cache line from the slab header at the 64 KB boundary of the freed pointer. That costs ~5 ns when the line is cold. In exchange, free() discovers the size class, the owning thread, and the region identity in a single load, with no page map, no radix tree, and no external metadata structure.

What Spaces gives you that malloc doesn't

Unlike typical drop-in allocators, Spaces also exposes region-style heaps, live-allocation walking, per-region memory ceilings, and cross-process shared heaps — 62 functions in the same binary.

Destroyable heaps

SpacesChunk parse_heap = spaces_chunk_create(0);
AstNode *root = parse(parse_heap, source);   // all nodes from this heap
// ... transform, analyse, error-check ...
spaces_chunk_destroy(parse_heap);            // one call frees the entire tree

This is the difference between leak-prone cleanup code and one O(1) destroy.

Heap introspection

printf("pool: %zu allocs, %zu bytes\n",
       spaces_chunk_count(conn_pool),
       spaces_chunk_size(conn_pool));

SpacesChunkEntry e;
while (spaces_chunk_walk(conn_pool, &e) == SPACES_CHUNK_OK)
    if (e.isInUse)
        printf("  live: %p  %zu bytes\n", e.entry, e.size);

No recompilation, no Valgrind, no profiler.

Memory budgets

spaces_chunk_set_ceiling(cache, 256 * 1024 * 1024);
// Every allocation that would push the chunk past 256 MB returns NULL
// or invokes your error handler.

Fixed-size fast path

For uniform-sized objects (linked list nodes, connection structs): zero per-allocation overhead, zero fragmentation, O(1) alloc and free.

SpacesChunk pool = spaces_chunk_create_fixed(sizeof(Node), 0, 0);
Node *n = spaces_chunk_alloc_fixed(pool);

Cross-process shared heaps

// On UNIX the name parameter is a System V key, not a string.
key_t key = ftok("/tmp/myapp", 'A');
SpacesChunk shm = spaces_chunk_create_shared((const char *)(intptr_t)key, 64*1024*1024, 0);
// Another process attaches with the same key:
SpacesChunk shm = spaces_chunk_attach_shared(NULL, (const char *)(intptr_t)key);

Per-chunk tuning

Every chunk has its own page size, growth increment, small-block threshold, and sub-chunk count. Tune the hot chunk for throughput, the cold chunk for density.

Thread-exclusive mode

Tell the allocator a thread owns all its allocations. It drops locking and auto-frees everything at thread exit.

spaces_default_chunk_thread_exclusive(
    SPACES_THREAD_EXCLUSIVE_ON | SPACES_FREE_ON_THREAD_TERM);

Error callbacks

// Replace the default interactive handler for non-interactive programs.
// Double frees, bad pointers, ceiling violations — caught and reported
// through your callback instead of silent corruption.
spaces_set_error_handler(my_handler);

Performance

Competitive with common allocators on standard benchmarks. The table below summarises both wins and losses.

Workload What it tests Spaces Standing
larson cross-thread producer-consumer 124 M ops/s #2 — beats jemalloc, snmalloc, tcmalloc, Hoard
malloc-large 5–25 MB blocks 6.0 ms/alloc #2 — beats jemalloc, snmalloc, Hoard
mleak thread create/destroy 0.26 s #2 — beats jemalloc, snmalloc
glibc-simple single-thread alloc/free 6.9 s #3 — beats jemalloc, snmalloc
xmalloc-test producer-consumer 10.6 M/s #3 — beats snmalloc, tcmalloc
mstress 16-thread mixed 6.9 s #3 — beats tcmalloc, Hoard
spacesbench 16-thread histogram 4.0 s #3 — beats snmalloc, tcmalloc, Hoard
glibc-thread 8-thread random 493 M iter #6

Top 3 on 7 of 8 workloads. The one loss (glibc-thread) comes from the slab-header cache miss on free() when allocation sizes vary widely across many threads.

Reproduce: ./bench.sh (auto-downloads workloads from mimalloc-bench, builds them against libspaces.a, and reports median time and peak RSS over 3 runs). Use ./bench.sh -s to skip the download on subsequent runs, or ./bench.sh -n N to change the number of repetitions.

Honest limitations

  • Heavy thread-churn workloads — the per-thread slab working set across 52 size classes doesn't stay cache-hot when 500 threads cycle rapidly.

  • x86-64 Linux only in this build. The source support for other platforms will be added soon but this single-file distribution is pre-processed for one target.

  • Minimum allocation is 8 bytes. No 4-byte or 2-byte classes.

  • 64 KB virtual footprint per slab even if few objects are live.

  • Every free() reads the slab header — one potential L1 miss per free. This is the core trade-off for the zero-external-metadata design.

How it works

  • 64 KB-aligned slabs. free(ptr) finds metadata by masking: ptr & ~0xFFFF. No page map, no radix tree.
  • 52 size classes (8 B – 8 KB), direct lookup table.
  • Per-thread intrusive freelist cache: 16 bytes/bin, 52 bins, 832 bytes total — fits in 13 cache lines.
  • Same-thread malloc/free fast path: zero atomics, zero locks.
  • Cross-thread free via ABA-safe Treiber stack with pointer folding.
  • Large allocations (> 8 KB) direct from mmap with huge-page support.
  • Batch segment allocation: 4 MB regions, one syscall per 64 slabs.
  • Heap pooling: thread create/destroy reuses pre-built heap state.
Design choice Benefit Cost
64 KB aligned slabs O(1) metadata lookup 64 KB min virtual footprint
4 KB slab header memalign ≤ 4096 on fast path 6% waste per segment
Intrusive freelist 832 B total cache, L1-friendly overwrites first 8 bytes of freed object
Per-slab remote-free stack no global lock on cross-thread free one CAS per remote free
4 MB batch regions ~1 mmap per 64 slabs coarser virtual granularity
No MADV_FREE on return faster slab reuse RSS stays elevated until reuse

Build

gcc -O3 -pthread -fPIC -c spaces.c
ar rc libspaces.a spaces.o && ranlib libspaces.a

Drop-in malloc (replaces libc):

gcc app.c -Wl,--whole-archive libspaces.a -Wl,--no-whole-archive -lpthread

Chunk API only (libc malloc untouched):

gcc app.c libspaces.a -lpthread

Self-test:

make test

Benchmarks:

./bench.sh            # download (once), build, run all
./bench.sh -s         # skip download, rebuild + run
./bench.sh -n 5       # 5 repetitions per test

Usage

Using Spaces on Linux

Include spaces.h in every translation unit that calls the Spaces API. If the file also includes headers that declare malloc, such as <stdlib.h>, include those headers first and spaces.h afterward.

Example:

#include <stdlib.h>
#include "spaces.h"

If the compiler does not already search the directory that contains spaces.h, add the current project directory to the include path. In this distribution the public header sits next to spaces.c, so a local build typically uses:

-I.

Multithreading

The core allocator (malloc, calloc, realloc, free) is fully thread-safe and self-initializing. Multiple threads may call these functions concurrently without any explicit setup — the library performs lock-free, atomic initialization on the first allocation.

The chunk subsystem (spaces_chunk_create*, spaces_chunk_alloc, etc.) also initializes on demand. Calling spaces_init() explicitly is optional; do so only when you need to guarantee that process-wide state is configured before any allocation occurs (for example, to set spaces_default_page_size before the default chunk is created).

The process-wide default chunk used by the C allocation wrappers is serialized unless you deliberately disable that behavior through spaces_default_flags. Chunks created explicitly with spaces_chunk_create*() are not serialized unless you pass SPACES_CHUNK_DEFAULT or SPACES_CHUNK_SERIALIZE. If a chunk is touched by only one thread at a time, leave serialization off to avoid unnecessary contention.

Dynamically loaded shared libraries

When Spaces is linked into a shared object that is loaded later with dlopen(), the process may already have resolved malloc to the C runtime before your module is loaded. In that situation your module can still end up calling the CRT allocator unless the shared object is linked with symbolic binding. Use -Bsymbolic when linking the shared library if you need the library's internal calls to resolve to Spaces at runtime.

Shared-memory chunks on UNIX

Shared chunks are created with spaces_chunk_create_shared() and attached with spaces_chunk_attach_shared(). On UNIX these functions use System V shared memory (shmget/shmat). The name parameter is not a string — it is a key_t value that must be cast to const char * for the call. The typical pattern is:

#include <sys/ipc.h>
key_t key = ftok("/tmp/myapp", 'A');
SpacesChunk shm = spaces_chunk_create_shared((const char *)(intptr_t)key, size, 0);

The (intptr_t) intermediate cast avoids sign-extension and pointer-width warnings. In the attaching process, use the same key:

SpacesChunk shm = spaces_chunk_attach_shared(NULL, (const char *)(intptr_t)key);

Shared chunks behave as though SPACES_CHUNK_SHARED and SPACES_CHUNK_SERIALIZE were already included. The caller therefore does not need to pass those flags again. The size argument is the total backing size of the shared-memory object. On UNIX it is fixed at creation time and cannot later be enlarged.

Capacity planning matters for shared chunks. Roughly 4 KB is consumed by chunk metadata, and in normal use a meaningful portion of the segment remains free space. A practical planning rule is to size a shared chunk to at least 2 * peak_payload + 4 KB if you expect to allocate peak_payload bytes at maximum load.

A shared chunk can be used concurrently by multiple processes. Error reports are emitted in the process that detects the fault, not necessarily the process that created the shared chunk. spaces_chunk_create_shared() must finish in the creating process before spaces_chunk_attach_shared() can succeed elsewhere because the synchronization primitive is stored inside the shared mapping and is not valid until initialization is complete.

To detach from a shared chunk in one process, call spaces_chunk_destroy() in that process. The shared object is removed only after the final attached process destroys it. Ownership is collective rather than tied to the creator.

User-supplied memory areas

spaces_chunk_create_area() and spaces_chunk_create_area_ex() let the allocator manage a memory region that you supply. The caller remains responsible for the lifetime of that region. Destroying the chunk releases allocator-side bookkeeping and synchronization objects, but it does not free the user-owned area itself.

Default Linux error handling

The built-in Linux error handler writes a diagnostic to stderr and, for recoverable failures, reads a single-character response from stdin (abort/ignore/retry). This interactive prompt is unsuitable for servers, daemons, CI, and any context where stdin is not a terminal.

Replace it early in main() for non-interactive programs:

static int my_handler(SpacesErrorInfo *err)
{
    fprintf(stderr, "spaces: error %d\n", err->errorCode);
    return 0;          // 0 = ignore (return error to caller)
}

int main(void)
{
    spaces_set_error_handler(my_handler);
    // ...
}

Returning 0 from the handler tells the allocator to propagate the failure to the caller as a NULL return or error code. Calling abort() inside the handler terminates the process immediately.

The built-in handler's three responses:

  • Abort: the handler calls abort() and terminates the process.
  • Ignore: the current Spaces API returns an error result to the caller.
  • Retry: the allocator immediately retries the operation.

Linux defaults and limits

The following values summarize the documented Linux defaults for this allocator:

Description 64-bit Value Related API
Default page size 64 KB spaces_chunk_set_page_size
Default small-block threshold 256 B spaces_chunk_set_small_block_size
Per-allocation overhead, fixed blocks 0 B spaces_chunk_alloc_fixed
Per-allocation overhead, blocks < 256 B 0 B spaces_chunk_alloc, malloc
Per-allocation overhead, var ptr blocks 2 B spaces_chunk_alloc, malloc
Allocation granularity 8 B all APIs
Minimum fixed block size 4 B spaces_chunk_alloc_fixed
Minimum variable block size 14 B spaces_chunk_alloc
Per-page allocator overhead 44 B internal
Minimum page size 4 KB spaces_chunk_set_page_size
Maximum page size 64 KB spaces_chunk_set_page_size
Empty chunk footprint 16 KB spaces_chunk_create*
Maximum sub-chunk count 256 spaces_chunk_set_max_subchunks
Process free-byte threshold ULONG_MAX spaces_process_set_free_bytes
Process heap limit ULONG_MAX spaces_process_set_heap_limit
Process grow increment 1 MB spaces_process_set_grow_increment
Chunk free-byte threshold 1 MB spaces_chunk_set_free_bytes
Large-block threshold 32 MB spaces_process_set_large_threshold

API Reference

This section documents the public API exported by spaces.h.

malloc

Prototype

void *malloc(size_t size);

Returns A pointer to at least size bytes, or NULL if the request cannot be satisfied.

Description Allocates from the process default chunk. The returned memory is not initialized. A request of zero bytes still goes through the allocator and is treated as a zero-length allocation case by this API.

Notes

  • The default chunk is created lazily on the first allocation unless you create it up front with spaces_init_default_chunk().
  • Use spaces_default_chunk with chunk APIs such as spaces_chunk_size() or spaces_chunk_shrink() when you need information about the heap backing malloc().
  • The actual allocation can be larger than the requested size because of alignment and allocator granularity; query it with spaces_chunk_ptr_size() or spaces_usable_size().

calloc

Prototype

void *calloc(size_t nobj, size_t size);

Returns A zero-filled allocation covering nobj * size bytes, or NULL on failure.

Description Identical to malloc() with respect to chunk ownership, but clears the full returned region before control returns to the caller.

Notes

  • The default chunk is used implicitly.
  • A zero factor or zero element size is treated as an allocation-zero condition by this API.

realloc

Prototype

void *realloc(void *ptr, size_t size);

Returns A pointer to the resized block, or NULL if the resize fails.

Description Resizes a block obtained from the default allocator entry points or chunk APIs. The block may move. If it moves, the original storage is released only after the contents have been copied.

Notes

  • Passing NULL behaves like malloc(size).
  • Passing a non-NULL pointer with size == 0 yields NULL and deallocates the original block.
  • Use spaces_chunk_realloc() when you need resize policy flags.

free

Prototype

void free(void *ptr);

Returns No value.

Description Releases a block obtained from the standard allocator wrappers or the compatible Spaces allocation APIs.

Notes

  • free(NULL) is a no-op.
  • Freeing from the wrong thread or freeing the same block twice is reported through the Spaces error path when error handling is enabled.

spaces_core_init

Prototype

void spaces_core_init(void);

Returns No value.

Description Initializes the modern slab allocator core used by the malloc-family wrappers.

Notes

  • Most applications do not need to call this directly because the first allocation path initializes the core lazily.

spaces_malloc

Prototype

void *spaces_malloc(size_t size);

Returns A newly allocated block or NULL.

Description Direct entry point to the Spaces-backed malloc implementation.

Notes

  • Useful when the archive is linked without replacing the CRT symbol but you still want the allocator explicitly.

spaces_calloc

Prototype

void *spaces_calloc(size_t nobj, size_t size);

Returns A zero-filled block or NULL.

Description Direct entry point to the Spaces-backed calloc implementation.

spaces_realloc

Prototype

void *spaces_realloc(void *ptr, size_t size);

Returns A resized block or NULL.

Description Direct entry point to the Spaces-backed realloc implementation.

spaces_memalign

Prototype

void *spaces_memalign(size_t alignment, size_t size);

Returns An aligned block or NULL.

Description Allocates storage whose address satisfies the requested power-of-two alignment.

Notes

  • Use this when object alignment matters more than minimizing internal slack.
  • Small-block allocations may otherwise be aligned only to word boundaries. If you require stronger alignment from general allocation traffic, either request it explicitly here or disable the small-block allocator for the relevant chunk.

spaces_usable_size

Prototype

size_t spaces_usable_size(void *ptr);

Returns The usable size of the allocation, or zero for an invalid pointer in the current implementation.

Description Reports the allocator's actual payload size for a live allocation.

Notes

  • This is the modern equivalent of asking how much space the allocator rounded the request up to.

spaces_thread_cleanup

Prototype

void spaces_thread_cleanup(void);

Returns No value.

Description Releases per-thread allocator state associated with the calling thread.

Notes

  • Relevant for applications that manage thread lifetime manually and want to force cleanup at a known point.

spaces_init

Prototype

int spaces_init(void);

Returns Non-zero on success, zero on failure.

Description Registers the process with the Spaces subsystem and initializes state that chunk APIs depend on.

Notes

  • The library performs implicit registration on demand, so calling spaces_init() is optional. Use it when you need process-wide configuration (page size, flags) to take effect before the first allocation triggers lazy initialization.
  • Registration is reference-counted. spaces_shutdown() must be called the same number of times to fully tear the subsystem down.

spaces_shutdown

Prototype

int spaces_shutdown(void);

Returns Non-zero if the process was registered and the count was decremented; zero if nothing was registered.

Description Unregisters the current process from the Spaces subsystem and frees allocator-owned global state when the registration count reaches zero.

Notes

  • All chunks, error handlers, and debug state owned by the process are released when the last registration goes away.

spaces_version

Prototype

SpacesVersion spaces_version(void);

Returns A packed integer version identifier.

Description Returns the allocator version in encoded major/minor/update form.

Notes

  • The older documentation refers to helper macros such as SPACES_MAJOR_VERSION(ver) for unpacking, but those macros are not part of the current public header in this build.

spaces_init_default_chunk

Prototype

SpacesChunk spaces_init_default_chunk(void);

Returns The process default chunk, or NULL if creation fails.

Description Creates the default chunk used by the standard allocation wrappers if it does not already exist, otherwise returns the existing chunk.

Notes

  • Call this early when you want to set chunk defaults before any startup code reaches malloc().
  • The initial default-chunk configuration is influenced by spaces_default_block_size, spaces_default_page_size, and spaces_default_flags.

spaces_free_default_chunk

Prototype

int spaces_free_default_chunk(void);

Returns Implementation-defined success indicator.

Description Destroys the process default chunk through the supported control path.

Notes

  • Do not pass spaces_default_chunk to spaces_chunk_destroy(). The default chunk has its own teardown API.

spaces_default_chunk_thread_exclusive

Prototype

int spaces_default_chunk_thread_exclusive(unsigned flags);

Returns Non-zero on success, zero on failure.

Description Switches the calling thread between the shared default chunk and a thread- private default chunk policy.

Notes

  • SPACES_THREAD_EXCLUSIVE_ON gives the thread its own default chunk and removes cross-thread synchronization for allocations done there.
  • SPACES_FREE_ON_THREAD_TERM additionally arranges for allocations from that thread-private chunk to be reclaimed when the thread exits.
  • Use this only when allocations created in the thread are also freed there, or are intentionally abandoned until thread termination.

spaces_chunk_create

Prototype

SpacesChunk spaces_chunk_create(unsigned flags);

Returns A new chunk handle or NULL.

Description Creates a general-purpose chunk for subsequent pointer-based or fixed-size allocations.

Notes

  • Use explicit chunks when you want memory ownership separated by subsystem, lifetime, locality, or synchronization policy.
  • Supported creation flags include SPACES_CHUNK_SERIALIZE, SPACES_CHUNK_VIRTUAL_LOCK, SPACES_CHUNK_ZERO_INIT, and SPACES_CHUNK_DEFAULT.

spaces_chunk_create_fixed

Prototype

SpacesChunk spaces_chunk_create_fixed(unsigned short block_size, size_t count, unsigned flags);

Returns A chunk handle or NULL.

Description Creates a chunk configured for fixed-size allocation and optionally preallocates space for an initial number of blocks.

Notes

  • The effective fixed block size can be rounded up for alignment. Query the final value with spaces_chunk_info() or spaces_chunk_ptr_size().
  • Conceptually this is equivalent to creating a chunk, setting the fixed block size, then preallocating fixed-size storage.

spaces_chunk_create_area

Prototype

SpacesChunk spaces_chunk_create_area(void *addr, size_t size, unsigned flags);

Returns A chunk handle or NULL.

Description Builds a chunk inside caller-supplied storage.

Notes

  • The allocator uses the contiguous region beginning at addr and extending for size bytes.
  • The caller keeps ownership of the underlying memory region even after the chunk is destroyed.
  • On Linux, provide naturally aligned storage if you want the full region to be usable.

spaces_chunk_create_area_ex

Prototype

SpacesChunk spaces_chunk_create_area_ex(void *addr, size_t size, unsigned flags, void *security);

Returns A chunk handle or NULL.

Description Extended form of spaces_chunk_create_area() with an extra platform- specific security attribute pointer.

Notes

  • The security argument exists for compatibility with platforms that associate access metadata with the mapping or object that backs the chunk.

spaces_chunk_create_shared

Prototype

SpacesChunk spaces_chunk_create_shared(const char *name, size_t size, unsigned flags);

Returns A shared chunk handle or NULL.

Description Creates a chunk whose storage can be attached from multiple processes.

Notes

  • On UNIX the name parameter is a System V key_t value cast to const char *, not a string. Use ftok() to obtain the key and (const char *)(intptr_t)key for the cast.
  • Shared chunks are serialized for inter-process safety and should be sized carefully because UNIX shared chunks are fixed-size once created.
  • This build exposes spaces_chunk_create_shared() and spaces_chunk_attach_shared() in the public interface.

spaces_chunk_attach_shared

Prototype

SpacesChunk spaces_chunk_attach_shared(SpacesChunk chunk, const char *name);

Returns A chunk handle in the current process or NULL.

Description Attaches an already-created shared chunk to the current process.

Notes

  • On UNIX, name is a key_t cast — see spaces_chunk_create_shared() above for the correct calling pattern.
  • Attachment cannot succeed until the creator has finished initializing the shared object and its synchronization state.

spaces_chunk_destroy

Prototype

int spaces_chunk_destroy(SpacesChunk chunk);

Returns Non-zero on success, zero on failure.

Description Releases all allocations owned by a chunk in one operation and destroys the chunk itself.

Notes

  • This is the preferred cleanup path for region-style lifetimes because it is faster and less error-prone than freeing every block individually.
  • Destroying a shared chunk detaches the current process; the shared object disappears only after the last attached process destroys it.

spaces_chunk_alloc

Prototype

void *spaces_chunk_alloc(SpacesChunk chunk, size_t size, unsigned flags);

Returns A pointer on success, otherwise NULL.

Description Allocates a variable-size block from a specific chunk.

Notes

  • Common flags are SPACES_FLAG_ZERO_INIT, SPACES_FLAG_NO_GROW, SPACES_FLAG_NO_EXTERNAL, and SPACES_FLAG_RESIZEABLE.
  • The exact payload size can exceed the request because of alignment and allocator granularity.
  • Using explicit chunks is the main difference from plain malloc().

spaces_chunk_alloc_fixed

Prototype

void *spaces_chunk_alloc_fixed(SpacesChunk chunk);

Returns A pointer or NULL.

Description Allocates one fixed-size element from a fixed-size chunk.

Notes

  • This path is intended for large populations of equal-sized objects such as list nodes, tree nodes, and small object pools.
  • It avoids fragmentation within the chunk and carries no per-allocation overhead in the documented Linux configuration.

spaces_chunk_alloc_aligned

Prototype

void *spaces_chunk_alloc_aligned(SpacesChunk chunk, size_t size, size_t alignment, unsigned flags);

Returns An aligned pointer or NULL.

Description Allocates from a specific chunk while enforcing a caller-specified alignment.

Notes

  • Use this if the default small-block alignment is not sufficient for the object representation you plan to store.

spaces_chunk_alloc_aligned_offset

Prototype

void *spaces_chunk_alloc_aligned_offset(SpacesChunk chunk, size_t size, size_t alignment, size_t offset, unsigned flags);

Returns An aligned pointer or NULL.

Description Like spaces_chunk_alloc_aligned(), but enforces alignment relative to an offset into the returned block.

Notes

  • This is useful when a structure header precedes the payload and the payload itself must satisfy the alignment rule.

spaces_chunk_free

Prototype

int spaces_chunk_free(void *ptr);

Returns Non-zero on success, zero on failure.

Description Frees a variable-size block previously allocated by Spaces.

Notes

  • For whole-region teardown, prefer spaces_chunk_destroy() rather than iterating over allocations one by one.

spaces_chunk_free_fixed

Prototype

int spaces_chunk_free_fixed(void *ptr);

Returns Non-zero on success, zero on failure.

Description Frees a block that was allocated with the fixed-size allocator.

Notes

  • Passing a variable-size block here is a type error and is reported as SPACES_ERR_NOT_FIXED_SIZE in the public error code set.

spaces_chunk_free_aligned

Prototype

int spaces_chunk_free_aligned(void *ptr);

Returns Non-zero on success, zero on failure.

Description Frees a block allocated by one of the aligned allocation entry points.

spaces_chunk_realloc

Prototype

void *spaces_chunk_realloc(void *ptr, size_t size, unsigned flags);

Returns A pointer to the resized block, or NULL.

Description Resizes an existing Spaces allocation with control over zero-fill, in-place behavior, and chunk growth policy.

Notes

  • SPACES_FLAG_RESIZE_IN_PLACE makes the call fail instead of moving the block.
  • SPACES_FLAG_ZERO_INIT clears the newly added portion when the block grows.
  • SPACES_FLAG_RESIZEABLE trades extra space for a better chance of a later growth succeeding in place.

spaces_chunk_realloc_aligned

Prototype

void *spaces_chunk_realloc_aligned(void *ptr, size_t size, size_t alignment, unsigned flags);

Returns A resized aligned block or NULL.

Description Aligned variant of spaces_chunk_realloc().

spaces_chunk_realloc_aligned_offset

Prototype

void *spaces_chunk_realloc_aligned_offset(void *ptr, size_t size, size_t alignment, size_t offset, unsigned flags);

Returns A resized aligned block or NULL.

Description Offset-aware aligned variant of spaces_chunk_realloc().

spaces_chunk_ptr_size

Prototype

size_t spaces_chunk_ptr_size(void *ptr);

Returns The allocator's exact size for the block, or SPACES_ERROR_RET on error.

Description Reports the real size of a block returned by Spaces.

Notes

  • Use this rather than assuming the allocator returned exactly the requested number of bytes.

spaces_chunk_size_aligned

Prototype

size_t spaces_chunk_size_aligned(void *ptr);

Returns The size associated with an aligned allocation.

Description Returns the managed size for an aligned allocation block.

spaces_chunk_ptr_check

Prototype

SpacesPointerStatus spaces_chunk_ptr_check(SpacesChunk chunk, void *ptr);

Returns SPACES_PTR_OK, SPACES_PTR_FREE, or SPACES_PTR_WILD.

Description Validates that a pointer refers to a recognized Spaces allocation and optionally that it belongs to a specific chunk.

Notes

  • If chunk is NULL, the function searches accessible chunks for ownership.
  • This API is primarily for diagnostics and is not suitable for hot paths.
  • Passing an interior pointer instead of the original base address is considered invalid.

spaces_chunk_check_aligned

Prototype

SpacesChunkStatus spaces_chunk_check_aligned(SpacesChunk chunk, void *ptr);

Returns A chunk status code.

Description Performs chunk validation in the aligned-allocation path.

Notes

  • This is part of the public header but is mostly relevant to validation and debugging flows rather than ordinary allocation traffic.

spaces_chunk_set_page_size

Prototype

unsigned spaces_chunk_set_page_size(SpacesChunk chunk, unsigned size);

Returns The previous page size on success, otherwise zero or an error-path value.

Description Changes the size of pages from which the chunk sub-allocates.

Notes

  • The allocator rounds the requested page size to a suitable supported value.
  • Existing pages keep their old size; only future growth uses the new page size.
  • For good sub-allocation performance, keep the page size at least four times larger than block sizes that are common in the chunk.

spaces_chunk_set_block_size

Prototype

int spaces_chunk_set_block_size(SpacesChunk chunk, unsigned short size);

Returns Non-zero on success, zero on failure.

Description Defines the fixed block size used by spaces_chunk_alloc_fixed().

Notes

  • It is invalid to set the fixed block size larger than the page size.
  • Changing the fixed block size after fixed-size allocations exist is not supported.
  • This setting affects only the fixed-size allocator, not the small-block routing used by malloc() or spaces_chunk_alloc().

spaces_chunk_set_small_allocator

Prototype

int spaces_chunk_set_small_allocator(SpacesChunk chunk, SpacesSmallAllocator alg);

Returns Non-zero on success, zero on failure.

Description Selects the algorithm used for small allocations in a chunk.

Notes

  • SPACES_SMALL_NONE disables the specialized small-block path.
  • SPACES_SMALL_V3 is the older heterogeneous-page algorithm with one byte of per-block overhead and no cross-size recycling.
  • SPACES_SMALL_V5 is the newer zero-overhead homogeneous-page design that can recycle page space across block sizes and chunk uses.

spaces_chunk_set_small_block_size

Prototype

int spaces_chunk_set_small_block_size(SpacesChunk chunk, unsigned short size);

Returns Non-zero on success, zero on failure.

Description Sets the threshold below which variable-size allocations are routed to the small-block allocator.

Notes

  • The documented default is 256 bytes.
  • Raising the threshold usually improves speed but can increase retained memory because freed small blocks stay available for reuse in the same size class.
  • Set the threshold to zero to disable small-block routing for variable-size APIs without disabling fixed-size allocation support.

spaces_chunk_set_floor

Prototype

size_t spaces_chunk_set_floor(SpacesChunk chunk, size_t floor);

Returns The previous floor value, or SPACES_ERROR_RET on failure.

Description Sets the minimum chunk footprint that shrink operations should preserve.

Notes

  • The floor acts like a ratchet. It does not force immediate growth, but once the chunk grows past it, the allocator avoids shrinking below it.
  • Very large external allocations can still let the chunk dip below the floor when those external blocks are returned directly to the operating system.

spaces_chunk_set_ceiling

Prototype

size_t spaces_chunk_set_ceiling(SpacesChunk chunk, size_t ceiling);

Returns The previous ceiling value, or SPACES_ERROR_RET on failure.

Description Caps the total amount of operating-system memory a chunk may consume.

Notes

  • An allocation that would exceed the ceiling raises SPACES_ERR_EXCEEDED_CEILING and may trigger internal compaction and shrink attempts before ultimately failing.
  • A ceiling below the current chunk size or below the floor is invalid and has no effect.

spaces_chunk_set_free_bytes

Prototype

size_t spaces_chunk_set_free_bytes(SpacesChunk chunk, size_t bytes);

Returns The previous retained-free-byte threshold.

Description Controls how much empty page capacity a chunk keeps available for reuse.

Notes

  • Higher values improve performance and reduce contention because the chunk can recycle empty pages without taking a global path.
  • The documented default retained free space per chunk is 1 MB.

spaces_chunk_set_grow_increment

Prototype

size_t spaces_chunk_set_grow_increment(SpacesChunk chunk, size_t inc);

Returns The previous increment.

Description Sets the chunk-level growth quantum used when the chunk needs more backing memory.

Notes

  • Use this to tune between allocation burst efficiency and incremental footprint growth.

spaces_chunk_set_max_suballoc

Prototype

unsigned spaces_chunk_set_max_suballoc(SpacesChunk chunk, unsigned size);

Returns The previous sub-allocation threshold.

Description Sets the largest size that may still be carved out of an existing page instead of receiving a dedicated page.

Notes

  • A 16 KB threshold is often a good speed/space tradeoff, while some UNIX workloads perform better with a 64 KB threshold.
  • This knob is workload-sensitive; measure rather than assuming a universal optimum.

spaces_chunk_set_max_subchunks

Prototype

int spaces_chunk_set_max_subchunks(SpacesChunk chunk, unsigned count);

Returns Non-zero on success, zero on failure.

Description Caps how many sub-chunks a serialized chunk may create to reduce contention in SMP workloads.

Notes

  • A lower cap reduces footprint but can increase lock contention.
  • Do not reduce this below the processor count if low contention matters.

spaces_chunk_set_serialization

Prototype

int spaces_chunk_set_serialization(SpacesChunk chunk, int enable);

Returns The previous serialization setting.

Description Turns chunk serialization on or off at runtime.

Notes

  • This is meant for applications that are mostly single-threaded but occasionally need a shared chunk.
  • Disable serialization only when the chunk is guaranteed to be touched by one thread at a time.

spaces_chunk_set_page_resizing

Prototype

int spaces_chunk_set_page_resizing(SpacesChunk chunk, int enable);

Returns Non-zero on success, zero on failure.

Description Enables or disables variable page resizing for the chunk.

Notes

  • Resizing can favor space efficiency, while fixed 64 KB pages can favor speed for very large populations of similarly sized small blocks.

spaces_chunk_preallocate

Prototype

size_t spaces_chunk_preallocate(SpacesChunk chunk, size_t bytes, SpacesBlockType type);

Returns The number of bytes actually added, or SPACES_ERROR_RET on error.

Description Pulls backing memory into a chunk before the application needs it and formats that memory as free blocks of the requested type.

Notes

  • Useful when you want to front-load system call and initialization cost before a latency-sensitive phase.
  • Also useful with spaces_chunk_set_floor() when you want to reserve memory for a chunk ahead of time and keep it from being returned immediately.

spaces_chunk_shrink

Prototype

size_t spaces_chunk_shrink(SpacesChunk chunk);

Returns The number of bytes returned to the operating system, or zero if no shrink was possible.

Description Compacts moveable pages and frees page backing that is no longer needed.

Notes

  • Shrink never invalidates live allocations.
  • The allocator may call this automatically when an allocation fails because of memory pressure or a chunk ceiling.
  • The chunk is never reduced below its floor.

spaces_chunk_size

Prototype

size_t spaces_chunk_size(SpacesChunk chunk);

Returns The total OS memory currently consumed by the chunk.

Description Reports footprint, not payload usage.

Notes

  • To estimate live versus free space, combine this with spaces_chunk_walk() or your own accounting.

spaces_chunk_count

Prototype

size_t spaces_chunk_count(SpacesChunk chunk);

Returns The net number of allocations from the chunk.

Description Reports allocations minus frees across the pages owned by the chunk.

Notes

  • This is a convenient approximation of logical element count if one chunk backs one data structure.

spaces_chunk_info

Prototype

int spaces_chunk_info(SpacesChunk chunk, void *ptr, SpacesChunkInfo *info);

Returns Non-zero on success, zero on failure.

Description Fills a SpacesChunkInfo structure using either a chunk handle, a pointer owned by a chunk, or both.

Notes

  • If chunk is NULL and ptr is not, the function resolves chunk ownership from the pointer.
  • The returned type field is a bitwise combination describing what block kinds are currently allocated in the chunk.

spaces_chunk_first

Prototype

SpacesChunkStatus spaces_chunk_first(SpacesChunkInfo *info, int all_tasks);

Returns SPACES_CHUNK_OK, SPACES_CHUNK_END, or a corruption status.

Description Begins enumeration of existing chunks and writes metadata into info.

Notes

  • Use spaces_chunk_next() to continue the walk.
  • On Linux, all_tasks is a compatibility parameter.

spaces_chunk_next

Prototype

SpacesChunkStatus spaces_chunk_next(SpacesChunkInfo *info, int all_tasks);

Returns SPACES_CHUNK_OK, SPACES_CHUNK_END, or a corruption status.

Description Continues a chunk enumeration started by spaces_chunk_first().

Notes

  • Do not create or destroy chunks while an enumeration is in progress.

spaces_chunk_walk

Prototype

SpacesChunkStatus spaces_chunk_walk(SpacesChunk chunk, SpacesChunkEntry *entry);

Returns SPACES_CHUNK_OK while entries remain, SPACES_CHUNK_END at completion, or a corruption status if validation fails.

Description Walks the blocks of a chunk one entry at a time and returns metadata for each block, free or in-use.

Notes

  • Initialize entry->entry to NULL before the first call.
  • Do not allocate from or free to the chunk between walk calls.
  • If other threads may touch the chunk, lock it for the duration of the walk.
  • This API exists for diagnostics and tooling, not for hot-path application logic.

spaces_chunk_check

Prototype

int spaces_chunk_check(SpacesChunk chunk);

Returns Non-zero if validation succeeds, zero if corruption is detected.

Description Validates every block in a chunk and reports corruption through the error handler.

Notes

  • Use this when tracking overwrites, freed-block writes, or general heap damage.
  • It works on both debug and runtime builds, although debug builds can report richer diagnostics.

spaces_chunk_lock

Prototype

int spaces_chunk_lock(SpacesChunk chunk);

Returns Non-zero on success, zero on failure.

Description Acquires thread-exclusive access to a serialized chunk.

Notes

  • Locking is meaningful only if the chunk was created with serialization enabled.
  • Every successful lock must be balanced by a corresponding spaces_chunk_unlock().
  • This is commonly paired with spaces_chunk_walk().

spaces_chunk_unlock

Prototype

int spaces_chunk_unlock(SpacesChunk chunk);

Returns Non-zero on success, zero on failure.

Description Releases one level of chunk-exclusive access previously acquired with spaces_chunk_lock().

Notes

  • Other threads remain blocked until the lock count returns to zero.

spaces_set_error_handler

Prototype

SpacesErrorFn spaces_set_error_handler(SpacesErrorFn fn);

Returns The previously installed handler.

Description Installs a process-local callback for allocator error reporting.

Notes

  • Passing NULL disables handler-based reporting and leaves APIs to fail by return value alone.
  • Handlers can be chained by forwarding unknown chunks or error conditions to the previous handler.
  • If the handler leaves via longjmp, exception throw, or another non-local exit, it must call spaces_error_unwind() immediately before doing so.

spaces_default_error_handler

Prototype

int spaces_default_error_handler(SpacesErrorInfo *err);

Returns Non-zero when the operation should be retried, zero otherwise.

Description Built-in fallback error handler used when no custom handler is installed.

Notes

  • On Linux it emits prompts to stderr with abort/ignore/retry behavior where retry is meaningful.
  • Call it from your own handler if you want default behavior for error cases you do not handle specially.

spaces_error_unwind

Prototype

void spaces_error_unwind(void);

Returns No value.

Description Tells the allocator that the current error handler will not return normally.

Notes

  • Only call this from inside a callback installed with spaces_set_error_handler().
  • Calling it and then returning normally from the handler is itself an error.

spaces_process_info

Prototype

int spaces_process_info(SpacesProcessInfo *info);

Returns Non-zero on success, zero on failure.

Description Reports process-wide allocator settings and totals.

Notes

  • The structure includes grow increment, retained free space, large-block threshold, heap limit, heap totals, and page-return policy settings.

spaces_process_set_grow_increment

Prototype

size_t spaces_process_set_grow_increment(size_t size);

Returns The previous process-wide increment.

Description Sets the minimum amount of memory the allocator requests from the operating system when feeding chunks from the shared process pool.

Notes

  • Larger increments reduce system call overhead and fragmentation but can increase footprint.
  • The value is rounded up to a 64 KB boundary.

spaces_process_set_free_bytes

Prototype

size_t spaces_process_set_free_bytes(size_t size);

Returns The previous process-wide retained-free-byte threshold.

Description Controls how much free space the large-block heap keeps before returning memory to the operating system.

Notes

  • Bigger thresholds favor performance; smaller thresholds favor tighter process footprint.

spaces_process_set_large_threshold

Prototype

size_t spaces_process_set_large_threshold(size_t size);

Returns The previous threshold.

Description Defines the size above which allocations are obtained directly from the operating system instead of the process large-block heap.

Notes

  • Higher thresholds keep more traffic in the reusable heap and can improve speed, but they also increase retained memory.

spaces_process_set_heap_limit

Prototype

size_t spaces_process_set_heap_limit(size_t size);

Returns The previous heap limit.

Description Sets an upper bound on the aggregate amount of operating-system memory the allocator may obtain for its own process-wide heap structures.

Notes

  • Exceeding the limit triggers SPACES_ERR_EXCEEDED_HEAP_LIMIT and can also surface as an out-of-memory condition higher in the stack.
  • This limit applies only to Spaces-managed heap acquisition, not to code, stack, static data, or unrelated mappings in the process.

spaces_process_use_munmap

Prototype

int spaces_process_use_munmap(int enable);

Returns The previous setting.

Description Controls whether UNIX builds should release allocator address space with munmap when possible.

Notes

  • Implementations primarily use madvise to discard pages while optionally using munmap for address-space release.
  • Once enabled, this setting is intended to remain on for the process lifetime.

spaces_process_coalesce_allocs

Prototype

int spaces_process_coalesce_allocs(int enable);

Returns The previous setting.

Description Controls whether large OS allocations may be coalesced into results that span underlying VM allocation boundaries.

Notes

  • This exists mainly as a workaround for platform subsystems that require one operating-system allocation per returned region.

spaces_process_flush_all

Prototype

void spaces_process_flush_all(void);

Returns No value.

Description Forces release of process-level allocator backing state after all chunks have already been destroyed.

Notes

  • This is mainly useful when unloading a module that statically linked Spaces.

spaces_process_disable_fork_handlers

Prototype

void spaces_process_disable_fork_handlers(void);

Returns No value.

Description Disables allocator-installed fork() handlers.

Notes

  • Use this only if your process has a concrete reason to manage post-fork allocator state without help from the library.

spaces_process_set_page_cache_size

Prototype

size_t spaces_process_set_page_cache_size(int page_multiple, size_t size);

Returns The previous page-cache setting.

Description Adjusts the process-level page cache used for recycled pages.

Notes

  • Tune this only after measurement; the tradeoff is more retained memory in exchange for less page churn and lower allocator latency.

Types, Constants, and Flags

Core public types

  • SpacesChunk: opaque handle for a managed chunk.
  • SpacesVersion: packed unsigned version value.
  • SpacesThreadId: unsigned long thread identifier.
  • SpacesErrorCode: allocator error code enumeration.
  • SpacesBlockType: describes fixed, variable, external, or free block classes.
  • SpacesSmallAllocator: selects the small-block allocation algorithm.
  • SpacesChunkStatus: result of chunk enumeration and chunk walk operations.
  • SpacesPointerStatus: result of pointer validation.
  • SpacesThreadExclusive: flags for thread-private default-chunk mode.
  • SpacesErrorFn: callback type used with spaces_set_error_handler().

Structures

SpacesErrorInfo

  • errorCode: the primary failure code.
  • chunk: chunk where the problem was detected.
  • argChunk, argPtr, argBuf, argSize, argCount, argFlags: call arguments associated with the failing operation.
  • file, line: source location metadata when available.
  • allocCount, passCount, checkpoint: allocator progress counters used by diagnostics.
  • errorAlloc: allocation object associated with the failure when relevant.
  • corruptAddr: address where corruption was found.
  • objectCreationInfo: nested creation-site information when the library preserves it.
  • threadID, pid: execution context of the report.
  • callStack[32]: captured stack frames up to SPACES_MAX_CALL_STACK.

SpacesChunkEntry

  • entry: current block address used by spaces_chunk_walk() as both cursor and output.
  • chunk: owning chunk.
  • type: block class flags.
  • isInUse: non-zero for live allocations, zero for free entries.
  • size: block size.
  • lockCount: chunk lock depth visible during the walk.
  • reserved_ptr, reserved_chunk: reserved fields.

SpacesChunkInfo

  • chunk: identified chunk.
  • type: currently present block categories.
  • blockSizeFS: configured fixed-size block size.
  • smallBlockSize: small-block routing threshold.
  • pageSize: current sub-allocation page size.
  • floor: configured minimum retained footprint.
  • ceiling: configured maximum footprint.
  • flags: chunk creation flags.
  • errorFn: installed error handler.

SpacesProcessInfo

  • growIncrement: process-wide growth quantum.
  • freeBytes: retained free-byte threshold.
  • largeBlockThreshold: size at which requests bypass the large-block heap.
  • heapLimit: configured process-wide cap.
  • heapTotal: total allocator-controlled OS memory.
  • totalFree: aggregate reusable free space.
  • useMunmap: whether address-space release via munmap is enabled.
  • coelesceSystemAllocs: whether operating-system allocations may be coalesced across allocation boundaries.

Error Codes

  • SPACES_OK: No error.
  • SPACES_ERR_INTERNAL: Internal allocator failure.
  • SPACES_ERR_OUT_OF_MEMORY: The operating system could not satisfy the request.
  • SPACES_ERR_BLOCK_TOO_BIG: The requested block or configured block size exceeds the supported limit for the current policy.
  • SPACES_ERR_ALLOC_ZERO: A zero-sized allocation or zero-sized fixed block request was made where the API treats that as invalid.
  • SPACES_ERR_RESIZE_FAILED: A resize request could not be completed under the active policy, especially with in-place-only resizing.
  • SPACES_ERR_LOCK: A locking or serialization operation failed.
  • SPACES_ERR_EXCEEDED_CEILING: The request would push a chunk past its configured ceiling.
  • SPACES_ERR_TOO_MANY_PAGES: A chunk exceeded its practical page-count limit; increasing page size can mitigate this.
  • SPACES_ERR_TOO_MANY_TASKS: Registration exceeded the supported task/process count for the subsystem.
  • SPACES_ERR_BAD_CHUNK: The supplied chunk handle is invalid or corrupted.
  • SPACES_ERR_BAD_BLOCK: A live block's metadata was damaged.
  • SPACES_ERR_BAD_FREE_BLOCK: A free block's metadata was damaged or a freed block was written after release.
  • SPACES_ERR_BAD_POINTER: The supplied pointer is not a valid Spaces allocation base pointer.
  • SPACES_ERR_WRONG_TASK: A non-shared chunk or pointer is being used from a thread/process/task that does not own it.
  • SPACES_ERR_NOT_FIXED_SIZE: A fixed-size-only API was given a non-fixed allocation.
  • SPACES_ERR_BAD_FLAGS: A flag combination is invalid for the target API.
  • SPACES_ERR_UNSUPPORTED: The platform or build does not support the requested operation.
  • SPACES_ERR_EXCEEDED_HEAP_LIMIT: The process-wide heap limit configured for Spaces was reached.
  • SPACES_ERR_LICENSE: Reserved license-related failure code.
  • SPACES_ERR_BAD_ALIGNMENT: The requested alignment is invalid.

Allocation Flags

  • SPACES_FLAG_FIXED (0x0000): fixed allocation semantics marker.
  • SPACES_FLAG_ZERO_INIT (0x0001): zero-fill newly allocated or newly grown bytes.
  • SPACES_FLAG_MOVEABLE (0x0002): allow movement when the allocator compacts or resizes.
  • SPACES_FLAG_RESIZEABLE (0x0004): reserve room that can improve later resize success.
  • SPACES_FLAG_RESIZE_IN_PLACE (0x0008): fail instead of moving on resize.
  • SPACES_FLAG_NO_GROW (0x0010): do not expand the chunk to satisfy the request.
  • SPACES_FLAG_NO_EXTERNAL (0x0020): forbid direct OS-backed external blocks.
  • SPACES_FLAG_NO_COMPACT (0x0040): disable compaction for the request path where applicable.
  • SPACES_FLAG_NO_SERIALIZE (0x0080): bypass serialization for the specific operation or mode where supported.

Chunk Creation Flags

  • SPACES_CHUNK_SHARED (0x0001): shared-memory chunk.
  • SPACES_CHUNK_SERIALIZE (0x0002): serialized chunk access.
  • SPACES_CHUNK_VIRTUAL_LOCK (0x0004): request locked memory pages.
  • SPACES_CHUNK_ZERO_INIT (0x0008): return zeroed memory from the chunk.
  • SPACES_CHUNK_AREA (0x0010): chunk backed by caller-supplied area.
  • SPACES_CHUNK_FILE_MAPPING (0x0020): file-mapped backing where supported.
  • SPACES_CHUNK_DEFAULT (0x8000): create with default chunk characteristics.

Block Type Flags

  • SPACES_BLOCK_FIXED (0x0001)
  • SPACES_BLOCK_VAR_MOVEABLE (0x0002)
  • SPACES_BLOCK_VAR_FIXED (0x0004)
  • SPACES_BLOCK_EXTERNAL (0x0008)
  • SPACES_BLOCK_FREE (0x0010)

Small-Block Allocator Modes

  • SPACES_SMALL_NONE: disable small-block acceleration.
  • SPACES_SMALL_V3: heterogeneous-page small-block allocator with 1 byte overhead per block.
  • SPACES_SMALL_V5: newer homogeneous-page allocator with zero per-block overhead and page recycling.

Chunk Walk Status Values

  • SPACES_CHUNK_OK (1): current result is valid and iteration may continue.
  • SPACES_CHUNK_CORRUPT (-1): corruption was found.
  • SPACES_CHUNK_CORRUPT_FATAL (-2): fatal corruption was found.
  • SPACES_CHUNK_END (0): iteration completed.

Pointer Validation Results

  • SPACES_PTR_OK (1): valid live allocation.
  • SPACES_PTR_WILD (0): not a recognized allocation base address.
  • SPACES_PTR_FREE (-1): valid block, but it has already been freed.

Thread-Exclusive Flags

  • SPACES_THREAD_EXCLUSIVE_OFF (0): use the shared process default chunk.
  • SPACES_THREAD_EXCLUSIVE_ON (0x10000): give the current thread a private default chunk.
  • SPACES_FREE_ON_THREAD_TERM (0x20000): free thread-private default-chunk allocations automatically when the thread exits.

Global Defaults and Constants

  • spaces_default_chunk: current default chunk used by standard allocation wrappers.
  • spaces_default_block_size: startup-time default fixed block size for the default chunk.
  • spaces_default_page_size: startup-time default page size for the default chunk.
  • spaces_default_flags: startup-time chunk creation flags for the default chunk.
  • spaces_malloc_linked: non-zero when malloc replacement is linked in.
  • SPACES_ERROR_RET: generic error sentinel cast from -1.
  • SPACES_UNLOCK_FAILED: unlock failure sentinel.
  • SPACES_MAX_CALL_STACK: maximum captured call-stack depth, currently 32.

Examples

The following snippets cover the major API categories. Each can be compiled directly against libspaces.a and spaces.h.

Drop-in malloc replacement

When linked with --whole-archive, malloc and free route through the Spaces slab engine automatically.

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

int main(void)
{
    /* Ordinary malloc — backed by Spaces when linked in. */
    double *v = malloc(1024 * sizeof *v);
    for (int i = 0; i < 1024; i++)
        v[i] = i * 0.5;
    printf("v[100] = %.1f\n", v[100]);
    free(v);
    return 0;
}

Fixed-size allocator

Ideal for linked lists, trees, or any uniform-sized node pool. Zero per-allocation overhead and zero fragmentation.

#include <stdio.h>
#include "spaces.h"

typedef struct Node {
    int key;
    struct Node *left, *right;
} Node;

int main(void)
{
    /* A chunk tuned for Node-sized allocations. */
    SpacesChunk pool = spaces_chunk_create_fixed(sizeof(Node), 0, 0);

    Node *root  = spaces_chunk_alloc_fixed(pool);
    root->key   = 50;
    root->left  = spaces_chunk_alloc_fixed(pool);
    root->left->key = 25;
    root->left->left = root->left->right = NULL;
    root->right = spaces_chunk_alloc_fixed(pool);
    root->right->key = 75;
    root->right->left = root->right->right = NULL;

    printf("root=%d left=%d right=%d\n",
           root->key, root->left->key, root->right->key);

    /* Release the entire pool in one call — no per-node free needed. */
    spaces_chunk_destroy(pool);
    return 0;
}

Variable-size allocation from a dedicated chunk

#include <stdio.h>
#include <string.h>
#include "spaces.h"

int main(void)
{
    SpacesChunk ch = spaces_chunk_create(0);

    char *greeting = spaces_chunk_alloc(ch, 64, 0);
    snprintf(greeting, 64, "Hello from a Spaces chunk");
    printf("%s (usable: %zu bytes)\n",
           greeting, spaces_chunk_ptr_size(greeting));

    spaces_chunk_free(greeting);
    spaces_chunk_destroy(ch);
    return 0;
}

Aligned allocation

#include <stdio.h>
#include <stdint.h>
#include "spaces.h"

int main(void)
{
    SpacesChunk ch = spaces_chunk_create(0);

    /* 4096-byte aligned buffer, useful for DMA or page-aligned I/O. */
    void *buf = spaces_chunk_alloc_aligned(ch, 8192, 4096, 0);
    printf("aligned ptr: %p (mod 4096 = %lu)\n",
           buf, (unsigned long)((uintptr_t)buf % 4096));

    spaces_chunk_free_aligned(buf);
    spaces_chunk_destroy(ch);
    return 0;
}

Chunk inspection

#include <stdio.h>
#include "spaces.h"

int main(void)
{
    SpacesChunk ch = spaces_chunk_create(0);

    for (int i = 0; i < 500; i++)
        spaces_chunk_alloc(ch, 48 + (i % 200), 0);

    printf("live allocations: %zu\n", spaces_chunk_count(ch));
    printf("total bytes:      %zu\n", spaces_chunk_size(ch));
    spaces_chunk_shrink(ch);
    printf("after shrink:     %zu\n", spaces_chunk_size(ch));

    spaces_chunk_destroy(ch);
    return 0;
}

Chunk walk (heap enumeration)

#include <stdio.h>
#include "spaces.h"

int main(void)
{
    SpacesChunk ch = spaces_chunk_create(0);
    for (int i = 0; i < 10; i++)
        spaces_chunk_alloc(ch, 100, 0);

    SpacesChunkEntry entry;
    SpacesChunkStatus st = spaces_chunk_walk(ch, &entry);
    int n = 0;
    while (st == SPACES_CHUNK_OK) {
        if (entry.isInUse)
            n++;
        st = spaces_chunk_walk(ch, &entry);
    }
    printf("walked %d in-use entries\n", n);

    spaces_chunk_destroy(ch);
    return 0;
}

Chunk configuration

#include <stdio.h>
#include "spaces.h"

int main(void)
{
    SpacesChunk ch = spaces_chunk_create(SPACES_CHUNK_SERIALIZE);

    /* Tune page size, growth, and ceiling. */
    spaces_chunk_set_page_size(ch, 32768);
    spaces_chunk_set_grow_increment(ch, 1024 * 1024);
    spaces_chunk_set_ceiling(ch, 64 * 1024 * 1024);
    spaces_chunk_set_free_bytes(ch, 512 * 1024);
    spaces_chunk_set_small_block_size(ch, 512);

    void *p = spaces_chunk_alloc(ch, 256, SPACES_FLAG_ZERO_INIT);
    printf("alloc OK, ptr=%p\n", p);

    spaces_chunk_free(p);
    spaces_chunk_destroy(ch);
    return 0;
}

Error handling

#include <stdio.h>
#include "spaces.h"

static int my_handler(SpacesErrorInfo *err)
{
    fprintf(stderr, "Spaces error %d\n", err->errorCode);
    return 0; /* 0 = do not retry */
}

int main(void)
{
    spaces_set_error_handler(my_handler);
    spaces_init();

    SpacesChunk ch = spaces_chunk_create(0);
    spaces_chunk_set_ceiling(ch, 4096);

    /* Allocate until the ceiling is hit. */
    for (int i = 0; i < 1000; i++) {
        void *p = spaces_chunk_alloc(ch, 64, 0);
        if (!p) {
            printf("allocation %d returned NULL (ceiling enforced)\n", i);
            break;
        }
    }

    spaces_chunk_destroy(ch);
    spaces_shutdown();
    return 0;
}

Thread-exclusive default chunk

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "spaces.h"

static void *worker(void *arg)
{
    int id = (int)(long)arg;

    /* This thread will free everything it allocates. */
    spaces_default_chunk_thread_exclusive(
        SPACES_THREAD_EXCLUSIVE_ON | SPACES_FREE_ON_THREAD_TERM);

    for (int i = 0; i < 10000; i++) {
        void *p = malloc(64 + (i & 0xFF));
        *(volatile char *)p = (char)i;
        free(p);
    }
    printf("thread %d done\n", id);
    return NULL;
}

int main(void)
{
    spaces_init();
    pthread_t t[4];
    for (int i = 0; i < 4; i++)
        pthread_create(&t[i], NULL, worker, (void *)(long)i);
    for (int i = 0; i < 4; i++)
        pthread_join(t[i], NULL);
    spaces_shutdown();
    return 0;
}

Process-level tuning

#include <stdio.h>
#include "spaces.h"

int main(void)
{
    spaces_init();

    spaces_process_set_grow_increment(2 * 1024 * 1024);
    spaces_process_set_free_bytes(4 * 1024 * 1024);
    spaces_process_set_heap_limit(256 * 1024 * 1024);
    spaces_process_set_large_threshold(1024 * 1024);

    SpacesProcessInfo info;
    spaces_process_info(&info);
    printf("grow increment: %zu\n", info.growIncrement);
    printf("heap limit:     %zu\n", info.heapLimit);

    spaces_shutdown();
    return 0;
}

Realloc and size query

#include <stdio.h>
#include <string.h>
#include "spaces.h"

int main(void)
{
    SpacesChunk ch = spaces_chunk_create(0);

    char *buf = spaces_chunk_alloc(ch, 32, 0);
    strcpy(buf, "short");

    /* Grow the buffer. */
    buf = spaces_chunk_realloc(buf, 256, 0);
    strcat(buf, " — now with room to spare");
    printf("%s (size: %zu)\n", buf, spaces_chunk_ptr_size(buf));

    spaces_chunk_free(buf);
    spaces_chunk_destroy(ch);
    return 0;
}

License

MIT — see LICENSE.

Copyright (c) 2021-2026 Praveen Vaddadi <thynktank@gmail.com>

About

A high-performance C allocator with explicit heap regions, fragmentation control, and runtime tuning.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors