How to Switch Between the Default and Stdlib Allocators at Runtime in Mulle-Allocator

You can switch between mulle_allocator_default and mulle_allocator_stdlib at runtime either by passing explicit allocator pointers to individual allocation calls or by overwriting the global mulle_allocator_default struct with the contents of mulle_allocator_stdlib.

The mulle-c/mulle-allocator library provides flexible memory management by exposing two distinct global allocator instances that can be toggled at runtime without recompiling. Whether you need to interoperate with code expecting standard libc behavior or debug allocation strategies, understanding how to switch between the default allocator and stdlib allocator at runtime gives you fine-grained control over memory operations.

Understanding the Global Allocator Instances

Mulle-Allocator ships with two ready-to-use global structs defined in [src/mulle-allocator.h](https://github.com/mulle-c/mulle-allocator/blob/master/src/mulle-allocator.h):

MULLE__ALLOCATOR_GLOBAL struct mulle_allocator   mulle_allocator_default;
MULLE__ALLOCATOR_GLOBAL struct mulle_allocator   mulle_allocator_stdlib;

mulle_allocator_default serves as the library's fallback allocator. When any public allocation function receives a NULL allocator pointer, it automatically routes the call through this global instance.

mulle_allocator_stdlib provides a thin wrapper around the C standard library functions (calloc, realloc, free). The implementation in [src/mulle-allocator.c](https://github.com/mulle-c/mulle-allocator/blob/master/src/mulle-allocator.c) initializes this struct with function pointers to static wrappers:

static void *v_calloc(size_t n, size_t size, struct mulle_allocator *allocator) { … }
static void *v_realloc(void *block, size_t size, struct mulle_allocator *allocator) { … }
static void  v_free(void *block, struct mulle_allocator *allocator) { … }

struct mulle_allocator   mulle_allocator_stdlib = {
    v_calloc,
    v_realloc,
    v_free,
    mulle_allocation_fail,
    mulle_allocator_no_aba_abort,
    NULL
};

Method 1: Per-Call Selection

The safest approach to switch allocators at runtime is to pass explicit allocator pointers to individual API calls. This method avoids global state changes and ensures specific allocations use your chosen strategy without affecting other parts of the program.

// Use stdlib allocator for this specific allocation
void *p = mulle_allocator_malloc(&mulle_allocator_stdlib, 128);

// Use default allocator for another allocation
void *q = mulle_allocator_malloc(&mulle_allocator_default, 64);

// Standard calloc using default behavior
void *r = mulle_calloc(10, sizeof(int));  // Uses mulle_allocator_default internally

All public allocation functions—including mulle_allocator_malloc(), mulle_allocator_calloc(), and mulle_allocator_realloc()—accept an explicit struct mulle_allocator * as their first parameter. When you pass &mulle_allocator_stdlib, the call routes directly to the C standard library functions. When you pass &mulle_allocator_default or NULL, the call uses the library's default allocation strategy.

Method 2: Global Runtime Switch

To change the allocator for all subsequent calls that rely on the default (including those passing NULL), overwrite the global mulle_allocator_default struct at runtime. Because these are plain mutable structs rather than const pointers, standard C assignment works without special API calls.

/* Preserve the original default allocator for later restoration */
struct mulle_allocator saved_default = mulle_allocator_default;

/* Switch global default to use stdlib implementations */
mulle_allocator_default = mulle_allocator_stdlib;

/* All subsequent calls using NULL or implicit default now use stdlib */
void *p = mulle_malloc(256);   // Internally uses libc calloc/realloc/free
void *q = mulle_calloc(10, sizeof(char));  // Also uses stdlib

/* Restore the original default allocator */
mulle_allocator_default = saved_default;

This technique affects every allocation throughout the process that does not explicitly specify an allocator pointer. Use this approach when integrating with third-party libraries that expect standard libc allocation semantics or when profiling memory behavior under different allocation strategies.

Verifying the Active Allocator

When debugging or writing conditional logic based on the current allocator, use the mulle_allocator_is_stdlib_allocator() helper function defined in [src/mulle-allocator.c](https://github.com/mulle-c/mulle-allocator/blob/master/src/mulle-allocator.c):

if (mulle_allocator_is_stdlib_allocator(&mulle_allocator_default)) {
    printf("Currently using stdlib allocator\n");
} else {
    printf("Using custom or default allocator\n");
}

This function compares the function pointers within the struct to determine if the allocator forwards directly to standard library functions.

Summary

  • Mulle-Allocator provides two global instances: mulle_allocator_default (optimized default behavior) and mulle_allocator_stdlib (libc forwarding).
  • Per-call switching passes &mulle_allocator_default or &mulle_allocator_stdlib explicitly to allocation functions, avoiding global side effects.
  • Global switching assigns mulle_allocator_default = mulle_allocator_stdlib to redirect all default allocations process-wide.
  • Detection uses mulle_allocator_is_stdlib_allocator() to verify which implementation is active.
  • Both instances are defined in src/mulle-allocator.h and implemented in src/mulle-allocator.c.

Frequently Asked Questions

When you pass NULL as the allocator pointer to any Mulle-Allocator function, the call automatically falls back to mulle_allocator_default. This global struct contains the function pointers that determine actual memory operations, so overwriting mulle_allocator_default at runtime changes the behavior of all NULL-passing calls.

Is it safe to modify mulle_allocator_default at runtime in multithreaded programs?

Because mulle_allocator_default is a plain global struct (not thread-local), overwriting it affects all threads immediately. You should only perform global switches during initialization phases or when you can guarantee no other thread is performing allocations. For thread-safe per-call selection, always pass explicit allocator pointers rather than modifying global state.

How do I revert to the original default after switching to the stdlib allocator?

Store a copy of the original struct before switching: struct mulle_allocator saved = mulle_allocator_default;. After performing operations with the stdlib allocator, restore the original behavior with mulle_allocator_default = saved;. This preserves any internal state or configuration specific to the library's default allocator.

Can I detect if an arbitrary allocator pointer refers to the stdlib implementation?

Yes. Pass the allocator pointer to mulle_allocator_is_stdlib_allocator() as implemented in src/mulle-allocator.c. This function checks whether the allocator's function pointers match the standard library wrappers, returning a boolean indicating if the allocator is the stdlib instance or a custom implementation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →