zalloc.h is a single-header, high-performance memory management suite for C/C++.
It unifies three specialized allocators, Arenas (Linear), Pools (Fixed-Block), and Debug (Leak Detection), into a cohesive API. It is designed to replace standard malloc/free in systems where performance, cache locality, and predictable lifetimes are critical.
Unlike general-purpose allocators that must handle fragmentation and thread safety at a high cost, zalloc provides specialized tools for specific patterns: linear data processing (Arenas) and node-based structures (Pools).
-
Z-ARENA (Linear Allocator):
- Zero-Overhead: Allocations are just a pointer increment.
-
Instant Cleanup: Free millions of objects in
$O(1)$ by resetting the arena. - Cache Friendly: Data is packed sequentially in memory blocks.
- Ideal For: Per-frame data, request handling, parsing, loading levels.
-
Z-POOL (Fixed-Block Allocator):
- No Fragmentation: Slices memory into perfect fixed-size slots.
-
$O(1)$ Operations: Allocation and Free take constant time (free-list pop/push). - Ideal For: Graphs, Linked Lists, ECS Entities, and Stable Maps.
-
Z-DEBUG (Leak Detector):
- Drop-in Replacement: Wraps standard allocators to track file/line info.
- Buffer Guard: Detects buffer overflows (canaries) and double-frees.
- Leak Reporting: Prints a detailed report of leaked blocks on exit.
-
Zero Dependencies: No linking required. Just drop it in and go.
- Copy
zalloc.hto your project's include folder. - Create one C file to hold the implementation:
// zalloc.c
#define ZALLOC_IMPLEMENTATION
#include "zalloc.h"You can also do this directly from your source files without creating a new one.
Arenas are the fastest way to manage memory for temporary tasks.
#include "zalloc.h"
void process_request()
{
// Initialize.
zarena scratch;
zarena_init(&scratch);
// Allocate rapidly (no free needed per item).
int *data = zarena_alloc(&scratch, 100 * sizeof(int));
char *str = zarena_alloc(&scratch, 64);
// Realloc (Optimized to extend in-place if possible).
data = zarena_realloc(&scratch, data, 100*4, 200*4);
// Instant Cleanup (Frees all blocks at once).
zarena_free(&scratch);
}Tip: Use
zarena_reset(&scratch)to keep the memory blocks allocated but mark them as empty. This effectively makes memory allocation "free" for the next frame/request.
Pools are mathematically optimal for systems where you allocate and free many items of the same size.
#include "zalloc.h"
struct Node
{
int id;
float x, y;
};
void game_loop()
{
// Initialize pool for 'struct Node', 100 items per block.
zpool entities;
zpool_init(&entities, sizeof(struct Node), 100);
// Allocate (O(1)).
struct Node *n1 = zpool_alloc(&entities);
struct Node *n2 = zpool_alloc(&entities);
// Free individually (O(1)) - Returns slot to free list.
zpool_recycle(&entities, n1);
// Cleanup everything.
zpool_free(&entities);
}Use this module during development to catch leaks and corruption.
#include "zalloc.h"
void debug_mode()
{
// Use macros to auto-capture File and Line.
void *p = zdebug_malloc(100);
// ... forgot to free p ...
// Print report.
size_t leaks = zdebug_print_leaks();
// Output:
// [Leak] 100 bytes at 0x... (alloc: main.c:15).
}| Function | Description |
|---|---|
zarena_init(zarena *a) |
Initializes the arena structure. |
zarena_alloc(zarena *a, size_t size) |
Allocates size bytes. Returns NULL on failure. |
zarena_alloc_zero(zarena *a, size_t size) |
Allocates and zero-initializes memory (like calloc). |
zarena_alloc_align(zarena *a, size_t size, size_t align) |
Allocates memory with specific alignment (must be power of 2). |
zarena_realloc(zarena *a, void *ptr, size_t old_sz, size_t new_sz) |
Resizes memory. Optimized if ptr was the last allocation. |
zarena_reset(zarena *a) |
Resets usage counters but keeps allocated blocks for reuse. Fast clear. |
zarena_free(zarena *a) |
Frees all memory blocks and resets the struct. |
| Function | Description |
|---|---|
zpool_init(zpool *p, size_t item_size, size_t per_block) |
Initializes pool for items of item_size. |
zpool_alloc(zpool *p) |
Returns a pointer to a free slot. |
zpool_recycle(zpool *p, void *ptr) |
Returns a slot to the free list. |
zpool_free(zpool *p) |
Frees all underlying memory blocks. |
| Function | Description |
|---|---|
zdebug_malloc(size_t size) |
Allocates memory with header tracking file/line. |
zdebug_calloc(size_t n, size_t size) |
Allocates zero-initialized tracked memory. |
zdebug_realloc(void *ptr, size_t size) |
Reallocates tracked memory. Checks canaries/magic. |
zdebug_free(void *ptr) |
Frees memory. Validates guard bytes/magic before freeing. |
zdebug_print_leaks(void) |
Prints a summary of all currently allocated blocks to stderr. |
zdebug_register_atexit(void) |
Registers zdebug_print_leaks to run automatically when the program exits. |
zalloc was designed to power the z-libs ecosystem. You can override the memory backend of the other libraries to use an Arena or Pool.
This makes map_free instantaneous and puts all map buckets into contiguous cache lines.
// Define global context for the macros.
static zarena *g_map_arena = NULL;
// Override zmap memory macros.
#define Z_MAP_MALLOC(sz) zarena_alloc(g_map_arena, sz)
#define Z_MAP_CALLOC(n, sz) zarena_alloc_zero(g_map_arena, (n)*(sz))
#define Z_MAP_REALLOC(p, sz) zarena_realloc(g_map_arena, p, 0, sz)
#define Z_MAP_FREE(p) /* No-op: Arena handles free. */
#include "zmap.h"
// ... Register types ...
int main()
{
zarena a;
zarena_init(&a);
g_map_arena = &a; // Set context.
// Map uses arena memory automatically.
map_IntInt m = map_init(IntInt, NULL, NULL);
map_put(&m, 1, 100);
// Frees the map buckets instantly.
zarena_free(&a);
}You can configure zalloc behavior by defining these macros before including the header.
| Macro | Description | Default |
|---|---|---|
ZALLOC_ARENA_ONLY |
Only compile the Arena module (saves binary size). | Disabled |
ZALLOC_POOL_ONLY |
Only compile the Pool module. | Disabled |
ZALLOC_DEBUG_ONLY |
Only compile the Debug module. | Disabled |
Z_MALLOC, Z_FREE |
Override the underlying backend (e.g., use OS calls directly). | stdlib.h |
ZARENA_DEFAULT_BLOCK_SIZE |
Initial size of arena blocks in bytes. | 4096 |
ZDEBUG_LOCK |
Define mutex lock for thread-safe debug logging. | No-op |
MIT License. See LICENSE for details.