# How mulle_allocator_realloc Grows Buffers Dynamically in mulle-c

> Explore how mulle_allocator_realloc dynamically grows buffers in mulle-c. Learn how this primitive handles memory expansion and failure for robust applications.

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

---

**`mulle_allocator_realloc` acts as a customizable grow-in-place primitive that routes memory expansion requests through a `struct mulle_allocator` instance, invoking the configured `realloc` callback and automatically triggering the allocator’s failure handler if the underlying system returns `NULL`.**

The `mulle_allocator_realloc` function serves as the central dynamic memory growth mechanism in the **mulle-c/mulle-allocator** library. Unlike standard C `realloc`, this allocator-aware variant delegates all operations to callback functions stored in a configuration struct, enabling developers to inject custom memory management strategies while maintaining consistent error handling. Whether you are expanding an existing buffer or allocating initial storage, understanding this function’s internals ensures robust memory management in C applications.

## Architecture of mulle_allocator_realloc

The implementation separates public convenience wrappers from internal core logic across two primary source files in the repository.

### Public API Wrapper in mulle-allocator.h

The inline wrapper resides in **[`src/mulle-allocator.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.h)** (lines 70–74) and handles `NULL` allocator fallback before forwarding to the internal implementation:

```c
static inline void *mulle_allocator_realloc( struct mulle_allocator *p, 
                                             void *block, 
                                             size_t size)
{
    return( _mulle_allocator_realloc( p ? p : &mulle_allocator_default, block, size));
}

```

This design ensures that passing `NULL` as the allocator pointer automatically routes the request to `&mulle_allocator_default`, which uses the standard system `malloc`, `realloc`, and `free` callbacks.

### Core Implementation in mulle-allocator.c

The internal function `_mulle_allocator_realloc` in **[`src/mulle-allocator.c`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.c)** (lines 76–92) contains the actual growth logic:

```c
void *_mulle_allocator_realloc( struct mulle_allocator *p,
                                void *block,
                                size_t size)
{
    void *q;

    assert( size );

    q = (*p->realloc)( block, size, p);
    if( MULLE_C_UNLIKELY( ! q))
        (*p->fail)( p, block, size);
    return( q);
}

```

Key implementation details include:

- **Size validation**: The function asserts that `size` is non-zero, distinguishing it from the strict variant.
- **Callback invocation**: It calls the allocator’s `realloc` function pointer with the current block, requested size, and allocator context.
- **Branch prediction**: The `MULLE_C_UNLIKELY` macro optimizes for the success path.
- **Error handling**: If the callback returns `NULL`, the allocator’s `fail` handler executes immediately.

## Growing Buffers Dynamically

When expanding an existing memory region, `mulle_allocator_realloc` follows a predictable sequence that preserves data integrity while potentially relocating the buffer to a larger address.

### The Growth Process

1. **Initial allocation**: Begin with a pointer returned by `mulle_allocator_malloc` or `NULL` for fresh allocations.
2. **Request expansion**: Call `mulle_allocator_realloc` with the current pointer and larger `size`.
3. **Relocation handling**: The underlying allocator may copy data to a new address; always replace your pointer with the return value.
4. **Failure management**: If expansion fails, the configured `fail` callback triggers before the function returns.

### Practical Usage Example

The following pattern demonstrates growing a buffer from 16 to 64 bytes using the default allocator:

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

int main(void)
{
    /* 1. Allocate initial buffer */
    char *buf = mulle_allocator_malloc(NULL, 16);
    strcpy(buf, "hello");
    printf("buf@%p = \"%s\"\n", (void *)buf, buf);

    /* 2. Grow buffer dynamically */
    char *newbuf = mulle_allocator_realloc(NULL, buf, 64);
    if (!newbuf) {
        perror("realloc failed");
        return 1;
    }
    buf = newbuf;  // Critical: update pointer after potential relocation

    /* 3. Use expanded space */
    strcat(buf, ", world! This is a longer string.");
    printf("grown buf@%p = \"%s\"\n", (void *)buf, buf);

    /* 4. Release memory */
    mulle_allocator_free(NULL, buf);
    return 0;
}

```

In this example, passing `NULL` as the first argument utilizes the default allocator. For custom memory pools or tracing allocators, substitute a pointer to your configured `struct mulle_allocator`.

## Strict Variant: mulle_allocator_realloc_strict

For code requiring exact C standard `realloc` semantics—including the `size == 0` frees behavior—the library provides **`mulle_allocator_realloc_strict`** and its internal counterpart `_mulle_allocator_realloc_strict` (lines 82–86 in [`src/mulle-allocator.c`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.c)). The file **[`test/coverage/do-stuff.c`](https://github.com/mulle-c/mulle-allocator/blob/main/test/coverage/do-stuff.c)** demonstrates this variant in practice.

```c
buf = mulle_allocator_realloc_strict(NULL, buf, 0);   // Frees buf, returns NULL

```

**Key difference**: The strict variant explicitly handles zero-size requests by freeing the block and returning `NULL` before invoking the underlying realloc callback, whereas the standard `mulle_allocator_realloc` asserts that size is non-zero.

## Error Handling and the Fail Callback

The allocator’s fail-fast approach ensures that memory exhaustion never returns silently. When the underlying `realloc` callback cannot satisfy the request:

1. The function detects the `NULL` return using `MULLE_C_UNLIKELY` to optimize branch prediction for the success case.
2. It invokes `(*p->fail)(p, block, size)`, passing the allocator instance, original pointer, and requested size.
3. The default `fail` handler calls `abort()`, but applications can substitute custom handlers for logging, graceful degradation, or exception throwing via the `struct mulle_allocator` configuration.

Comprehensive API documentation is available in **[`dox/API_ALLOCATOR.md`](https://github.com/mulle-c/mulle-allocator/blob/main/dox/API_ALLOCATOR.md)**, which details callback signatures and allocator configuration options.

## Summary

- **`mulle_allocator_realloc`** in [`src/mulle-allocator.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.h) provides a type-safe inline wrapper that defaults to the global allocator when passed `NULL`.
- The internal **`_mulle_allocator_realloc`** in [`src/mulle-allocator.c`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.c) (lines 76–92) executes the actual growth by invoking the allocator’s `realloc` callback.
- **Dynamic growth** requires replacing the old pointer with the return value to handle potential memory relocation.
- **Strict semantics** are available via `mulle_allocator_realloc_strict`, which properly frees memory when `size` is zero, as demonstrated in [`test/coverage/do-stuff.c`](https://github.com/mulle-c/mulle-allocator/blob/main/test/coverage/do-stuff.c).
- **Automatic error handling** triggers the allocator’s `fail` callback on allocation failure, defaulting to program abort unless overridden.

## Frequently Asked Questions

### What is the difference between mulle_allocator_realloc and standard C realloc?

**`mulle_allocator_realloc`** routes requests through a `struct mulle_allocator` instance rather than calling the system `realloc` directly. This indirection allows swapping allocation strategies (debugging, memory pools, custom heaps) at runtime while providing consistent error handling via the configured `fail` callback. Unlike standard `realloc`, the non-strict variant asserts that `size` is non-zero.

### How does mulle_allocator_realloc handle NULL pointer inputs?

When the `block` parameter is `NULL`, `mulle_allocator_realloc` behaves exactly like `malloc`, allocating a new block of the specified `size`. The implementation passes `NULL` through to the underlying allocator’s `realloc` callback, which is expected to handle this case according to standard C semantics.

### What happens when mulle_allocator_realloc cannot allocate memory?

If the underlying `realloc` callback returns `NULL`, `mulle_allocator_realloc` immediately invokes the allocator’s `fail` function pointer with the allocator instance, original block, and requested size. By default, this callback aborts the program, but custom allocators can replace it with handlers that perform logging, garbage collection, or other recovery actions before potentially returning or terminating.

### When should I use mulle_allocator_realloc_strict instead of mulle_allocator_realloc?

Use **`mulle_allocator_realloc_strict`** when you require exact C standard semantics where passing `size` as 0 frees the memory block and returns `NULL`. The standard `mulle_allocator_realloc` asserts that `size` is non-zero and should be used when zero-size allocations represent programmer errors rather than valid free operations.