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.
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);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.
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.
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 treeThis is the difference between leak-prone cleanup code and one O(1) destroy.
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.
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.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);// 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);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.
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);// 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);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.
-
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.
- 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/freefast path: zero atomics, zero locks. - Cross-thread free via ABA-safe Treiber stack with pointer folding.
- Large allocations (> 8 KB) direct from
mmapwith 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 |
gcc -O3 -pthread -fPIC -c spaces.c
ar rc libspaces.a spaces.o && ranlib libspaces.aDrop-in malloc (replaces libc):
gcc app.c -Wl,--whole-archive libspaces.a -Wl,--no-whole-archive -lpthreadChunk API only (libc malloc untouched):
gcc app.c libspaces.a -lpthreadSelf-test:
make testBenchmarks:
./bench.sh # download (once), build, run all
./bench.sh -s # skip download, rebuild + run
./bench.sh -n 5 # 5 repetitions per testInclude 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.
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.
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 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.
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.
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.
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 |
This section documents the public API exported by spaces.h.
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_chunkwith chunk APIs such asspaces_chunk_size()orspaces_chunk_shrink()when you need information about the heap backingmalloc(). - The actual allocation can be larger than the requested size because of
alignment and allocator granularity; query it with
spaces_chunk_ptr_size()orspaces_usable_size().
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.
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
NULLbehaves likemalloc(size). - Passing a non-
NULLpointer withsize == 0yieldsNULLand deallocates the original block. - Use
spaces_chunk_realloc()when you need resize policy flags.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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, andspaces_default_flags.
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_chunktospaces_chunk_destroy(). The default chunk has its own teardown API.
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_ONgives the thread its own default chunk and removes cross-thread synchronization for allocations done there.SPACES_FREE_ON_THREAD_TERMadditionally 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.
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, andSPACES_CHUNK_DEFAULT.
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()orspaces_chunk_ptr_size(). - Conceptually this is equivalent to creating a chunk, setting the fixed block size, then preallocating fixed-size storage.
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
addrand extending forsizebytes. - 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.
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
securityargument exists for compatibility with platforms that associate access metadata with the mapping or object that backs the chunk.
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
nameparameter is a System Vkey_tvalue cast toconst char *, not a string. Useftok()to obtain the key and(const char *)(intptr_t)keyfor 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()andspaces_chunk_attach_shared()in the public interface.
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,
nameis akey_tcast — seespaces_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.
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.
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, andSPACES_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().
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.
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.
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.
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.
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_SIZEin the public error code set.
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.
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_PLACEmakes the call fail instead of moving the block.SPACES_FLAG_ZERO_INITclears the newly added portion when the block grows.SPACES_FLAG_RESIZEABLEtrades extra space for a better chance of a later growth succeeding in place.
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().
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().
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.
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.
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
chunkisNULL, 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.
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.
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.
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()orspaces_chunk_alloc().
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_NONEdisables the specialized small-block path.SPACES_SMALL_V3is the older heterogeneous-page algorithm with one byte of per-block overhead and no cross-size recycling.SPACES_SMALL_V5is the newer zero-overhead homogeneous-page design that can recycle page space across block sizes and chunk uses.
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.
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.
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_CEILINGand 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
chunkisNULLandptris not, the function resolves chunk ownership from the pointer. - The returned
typefield is a bitwise combination describing what block kinds are currently allocated in the chunk.
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_tasksis a compatibility parameter.
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.
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->entrytoNULLbefore 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.
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.
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().
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.
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
NULLdisables 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 callspaces_error_unwind()immediately before doing so.
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
stderrwith 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.
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.
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.
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.
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.
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.
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_LIMITand 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.
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
madviseto discard pages while optionally usingmunmapfor address-space release. - Once enabled, this setting is intended to remain on for the process lifetime.
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.
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.
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.
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.
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 withspaces_set_error_handler().
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 toSPACES_MAX_CALL_STACK.
SpacesChunkEntry
entry: current block address used byspaces_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 viamunmapis enabled.coelesceSystemAllocs: whether operating-system allocations may be coalesced across allocation boundaries.
- 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.
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.
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.
SPACES_BLOCK_FIXED(0x0001)SPACES_BLOCK_VAR_MOVEABLE(0x0002)SPACES_BLOCK_VAR_FIXED(0x0004)SPACES_BLOCK_EXTERNAL(0x0008)SPACES_BLOCK_FREE(0x0010)
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.
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.
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.
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.
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.
The following snippets cover the major API categories. Each can be compiled
directly against libspaces.a and spaces.h.
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;
}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;
}#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;
}#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;
}#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;
}#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;
}#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;
}#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;
}#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;
}#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;
}#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;
}MIT — see LICENSE.
Copyright (c) 2021-2026 Praveen Vaddadi <thynktank@gmail.com>