# Using mulle_allocator with Memory Pools or Region-Based Allocation

> Learn how mulle_allocator supports memory pools and region-based allocation with its pluggable callback interface. Optimize your memory management today.

- Repository: [mulle-c/mulle-allocator](https://github.com/mulle-c/mulle-allocator)
- Tags: how-to-guide
- Published: 2026-03-07

---

**Yes, mulle_allocator fully supports memory pools and region-based allocation through its pluggable callback interface, allowing you to substitute custom allocators or use the built-in `stdlib_nofree` variant for arena-style memory management.**

The mulle-c/mulle-allocator library decouples memory management from data structures by requiring an explicit `struct mulle_allocator *` argument for every allocation operation. This design enables you to redirect all heap operations into a contiguous memory region or pool allocator without modifying existing code, as implemented in [`src/mulle-allocator-struct.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator-struct.h) and [`src/mulle-allocator.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.h).

## How the Pluggable Allocator Interface Works

At the core of the library is the **callback table** defined in [`src/mulle-allocator-struct.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator-struct.h) (lines 66-68). The `struct mulle_allocator` contains function pointers for `calloc`, `realloc`, `free`, and failure handling, allowing you to replace the standard heap with any allocation strategy.

The library provides three built-in global instances declared in [`src/mulle-allocator.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.h) (lines 75-88):

- **`mulle_allocator_default`** – Uses the standard C library `malloc` family
- **`mulle_allocator_stdlib`** – Explicit stdlib-based allocator with standard free behavior  
- **`mulle_allocator_stdlib_nofree`** – Allocates from the heap but ignores free calls, enabling simple pool-like behavior

Because functions like `mulle_allocator_malloc`, `mulle_calloc`, `mulle_realloc`, and `mulle_strdup` accept a `struct mulle_allocator *` parameter, you can pass a custom pool allocator to any data structure. The allocator propagates through the object graph, ensuring all nested allocations draw from the same region.

## Implementing a Bump-Pointer Arena Allocator

For high-performance scenarios requiring bulk deallocation, you can implement a **bump-pointer arena**. This pattern allocates a large contiguous block once, then hands out smaller pieces via pointer arithmetic. The `free` callback becomes a no-op, and the entire arena releases with a single system call.

The following example demonstrates a custom arena that plugs into the mulle-allocator interface:

```c
#include <mulle-allocator/mulle-allocator.h>
#include <stdlib.h>
#include <string.h>

static void *arena_base;
static size_t arena_offset;
static size_t arena_capacity;

static void arena_init(size_t reserve)
{
    arena_base = malloc(reserve);
    arena_offset = 0;
    arena_capacity = reserve;
}

static void *arena_malloc(struct mulle_allocator *alloc, size_t size)
{
    if (arena_offset + size > arena_capacity)
        return NULL;
    void *ptr = (char *)arena_base + arena_offset;
    arena_offset += size;
    return ptr;
}

static void *arena_realloc(struct mulle_allocator *alloc,
                           void *block, size_t size)
{
    if (block != (char *)arena_base + arena_offset - size)
        return NULL;
    if (arena_offset - size + size > arena_capacity)
        return NULL;
    arena_offset = arena_offset - size + size;
    return block;
}

static void arena_free(struct mulle_allocator *alloc, void *block)
{
    (void)alloc;
    (void)block;
}

static struct mulle_allocator arena_allocator = {
    .calloc = (void *(*)(size_t, size_t, struct mulle_allocator *))calloc,
    .realloc = arena_realloc,
    .free = arena_free,
    .fail = mulle_allocation_fail,
    .abafree = NULL,
    .aba = NULL
};

int main(void)
{
    arena_init(1 << 20);
    arena_allocator.calloc = (void *(*)(size_t,size_t,struct mulle_allocator *))
                              arena_malloc;

    char *s = mulle_allocator_strdup(&arena_allocator, "hello arena");
    printf("%s\n", s);

    free(arena_base);
    return 0;
}

```

**Key implementation details:**

- The `arena_allocator` satisfies the `struct mulle_allocator` contract defined in [`src/mulle-allocator-struct.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator-struct.h)
- `mulle_allocator_strdup` (declared in [`src/mulle-allocator.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.h)) draws from the arena instead of the heap
- Individual frees are no-ops; the entire region releases via `free(arena_base)`

## Using the Built-In stdlib_nofree Allocator

For simpler use cases where you need a static pool without implementing custom callbacks, the library provides **`mulle_allocator_stdlib_nofree`**. This allocator delegates allocation to `malloc` but substitutes a no-op for `free`, effectively creating a grow-only pool that persists for the process lifetime.

This approach works well for lookup tables or configuration data that lives for the entire execution:

```c
#include <mulle-allocator/mulle-allocator.h>

static void *static_pool;
static size_t pool_used;
static size_t pool_size;

static void static_pool_init(size_t size)
{
    static_pool = malloc(size);
    pool_used = 0;
    pool_size = size;
}

static void *pool_malloc(struct mulle_allocator *alloc, size_t size)
{
    if (pool_used + size > pool_size)
        return NULL;
    void *ptr = (char *)static_pool + pool_used;
    pool_used += size;
    return ptr;
}

static struct mulle_allocator pool_allocator = {
    .calloc = (void *(*)(size_t,size_t,struct mulle_allocator *))pool_malloc,
    .realloc = (void *(*)(void *,size_t,struct mulle_allocator *))realloc,
    .free = (void (*)(void *,struct mulle_allocator *))free,
    .fail = mulle_allocation_fail,
    .abafree = NULL,
    .aba = NULL
};

int main(void)
{
    static_pool_init(64 * 1024);

    int *vec = mulle_allocator_malloc(&pool_allocator, 10 * sizeof(int));
    for (int i = 0; i < 10; ++i)
        vec[i] = i * i;
    
    return 0;
}

```

While this example implements a custom wrapper, you can alternatively use `&mulle_allocator_stdlib_nofree` directly for heap-backed, never-free allocation behavior as documented in [`assets/dox/TOC.md`](https://github.com/mulle-c/mulle-allocator/blob/main/assets/dox/TOC.md).

## Embedding Pool Allocators in Data Structures

To ensure an entire object graph allocates from the same pool, **embed the allocator pointer** directly in your data structures. This pattern, described in the documentation at [`assets/dox/TOC.md`](https://github.com/mulle-c/mulle-allocator/blob/main/assets/dox/TOC.md) (line 24), allows objects to move between different memory schemes by swapping the allocator reference.

```c
typedef struct {
    struct mulle_allocator *alloc;
    int *data;
    size_t count;
    size_t capacity;
} int_vector;

static int_vector *int_vector_create(struct mulle_allocator *a)
{
    a = a ? a : &mulle_allocator_default;
    int_vector *v = mulle_allocator_malloc(a, sizeof(*v));
    v->alloc = a;
    v->capacity = 16;
    v->data = mulle_allocator_malloc(a, v->capacity * sizeof(int));
    v->count = 0;
    return v;
}

static void int_vector_push(int_vector *v, int value)
{
    if (v->count == v->capacity)
    {
        v->capacity *= 2;
        v->data = mulle_allocator_realloc(v->alloc, v->data,
                                           v->capacity * sizeof(int));
    }
    v->data[v->count++] = value;
}

static void int_vector_destroy(int_vector *v)
{
    mulle_allocator_free(v->alloc, v->data);
    mulle_allocator_free(v->alloc, v);
}

```

This approach guarantees that `int_vector` instances created with a pool allocator will return memory to that same pool during destruction, maintaining consistency across the object lifecycle.

## Summary

- The **pluggable allocator interface** in `struct mulle_allocator` enables substitution of custom memory management strategies without code modification
- **Bump-pointer arenas** implement the allocator callbacks to serve memory from a pre-allocated region with bulk deallocation
- The built-in **`mulle_allocator_stdlib_nofree`** provides ready-made, heap-backed pool behavior where individual frees are no-ops
- Embedding **allocator pointers** in data structures ensures the entire object graph uses consistent pool allocation
- All public API functions accept an explicit allocator argument, making region-based allocation transparent to library consumers

## Frequently Asked Questions

### Can I use mulle_allocator with existing memory pools?

Yes. By implementing the callback functions in `struct mulle_allocator` to wrap your existing pool's allocation and deallocation routines, you can pass the custom allocator to any mulle-allocator function. The library will route all `malloc`, `realloc`, and `free` operations through your pool's interface.

### What is the difference between stdlib and stdlib_nofree allocators?

The `mulle_allocator_stdlib` allocator uses standard `malloc`, `realloc`, and `free` behavior, while `mulle_allocator_stdlib_nofree` uses `malloc` for allocation but implements `free` as a no-op. The no-free variant is useful for arena-style allocation where you intend to release the entire memory region at once rather than individual blocks.

### How do I implement a bump-pointer allocator with mulle_allocator?

Implement custom `calloc` and `realloc` callbacks that return memory from a pre-allocated buffer using pointer arithmetic, and set the `free` callback to an empty function. Pass this custom `struct mulle_allocator` to functions like `mulle_allocator_malloc` or `mulle_allocator_strdup` to allocate from your arena.

### Can I switch an existing object to use a different memory pool?

Yes. If your data structure embeds a `struct mulle_allocator *` pointer, you can change the allocator reference at any time to redirect future allocations (such as `realloc` operations) to a different pool. Existing allocated blocks remain valid in their original pool until freed.