Difference between mulle_alloca_do and mulle_calloca_do in mulle-allocator

The only difference between mulle_alloca_do and mulle_calloca_do is that the former returns uninitialized memory (like malloc) while the latter returns zero-initialized memory (like calloc), with both using an identical stack-first allocation strategy that automatically falls back to the heap when the requested size exceeds 128 bytes.

The mulle-allocator library provides these macros in src/mulle-alloca.h as a portable, safe alternative to variable-length arrays and alloca(). Both allocate temporary buffers that automatically clean up when execution leaves the enclosing block, but they differ critically in their initialization guarantees and choice of fallback allocator.

Memory Initialization: The Primary Distinction

The fundamental distinction lies in what happens when the memory is first accessed. Both macros attempt to use a fixed-size stack buffer first (controlled by MULLE_ALLOCA_STACKSIZE, defaulting to 128 bytes), but behave differently when allocating from the heap or initializing the stack buffer.

mulle_alloca_do (Uninitialized)

As implemented at lines 280-296 of src/mulle-alloca.h, this macro:

  • Declares a stack buffer without initialization
  • Falls back to mulle_malloc for heap allocations
  • Leaves memory contents indeterminate—you must write before reading

Use this when you plan to immediately overwrite every element, such as when reading data from a file or socket into the buffer.

mulle_calloca_do (Zero-Initialized)

As implemented at lines 505-525 of src/mulle-alloca.h, this macro:

  • Declares the stack buffer with { 0 } initialization
  • Falls back to mulle_calloc for heap allocations
  • Guarantees zero-filled memory—safe to read without writing

Use this when constructing data structures with optional fields or when accumulating values where starting from zero is required.

Internal Implementation Details

Both macros expand to a nested for loop that manages the buffer lifecycle automatically. The mechanism works as follows:

  1. Stack reservation: A stack array of MULLE_ALLOCA_STACKSIZE bytes is declared
  2. Size check: The requested count is stored in a hidden pointer variable (e.g., name__count) and tested against the stack capacity using _mulle_alloca_do_get_size_as_length
  3. Allocation decision:
    • If the data fits on the stack, the pointer name references the stack array
    • If the data exceeds the stack size, mulle_malloc (for mulle_alloca_do) or mulle_calloc (for mulle_calloca_do) is called
  4. Automatic cleanup: When the for loop exits, _mulle_alloca_do_free releases the heap block if one was allocated, while stack memory is automatically reclaimed

The zero-initialization in mulle_calloca_do is achieved by the { 0 } initializer on the stack array and the zeroing behavior of mulle_calloc for heap fallbacks.

Practical Code Examples

Allocating Uninitialized Temporary Storage

When you will immediately write to every byte, use mulle_alloca_do to avoid the overhead of zeroing:

size_t n = 200;                     // Larger than default 128-byte stack buffer
mulle_alloca_do(buf, int, n)        // buf is an `int *`
{
    for (size_t i = 0; i < n; ++i)
        buf[i] = (int)i;            // Must write every element before use
    process_data(buf, n);
    /* buf is valid only inside this block */
}
/* Heap memory automatically freed here if n was large */

If n fits within 128 bytes, buf lives on the stack; otherwise mulle-allocator allocates via mulle_malloc.

Allocating Zero-Filled Temporary Storage

When you need to read from the buffer before writing, or require cleared memory:

size_t n = 50;
mulle_calloca_do(buf, double, n)    // buf is a `double *`
{
    /* All elements are already 0.0 – safe to read without overwriting */
    double sum = 0.0;
    for (size_t i = 0; i < n; ++i)
        sum += buf[i];              // Reading uninitialized memory would be safe here
    printf("Sum: %f\n", sum);
}

The stack buffer is declared as { 0 }, and any heap fallback uses mulle_calloc.

Dynamic Reallocation Within the Block

Both macros support resizing if you underestimate your needs:

size_t n = 20;
mulle_alloca_do(buf, char, n)
{
    strcpy(buf, "Hello");
    /* Need more space for concatenation */
    mulle_alloca_do_realloc(buf, 100);  // Moves to heap if necessary
    strcat(buf, " World!");
    puts(buf);
}

If the original allocation was on the stack, mulle_alloca_do_realloc automatically migrates the contents to a heap block and updates the pointer.

The src/mulle-alloca.h header provides additional utilities that work with both allocation types:

  • mulle_alloca_do_realloc / mulle_calloca_do_realloc: Resize the buffer, transitioning from stack to heap if the new size exceeds MULLE_ALLOCA_STACKSIZE
  • mulle_alloca_do_extract / mulle_calloca_do_extract: Move ownership of the buffer out of the block, returning a heap pointer that the caller must mulle_free
  • mulle_alloca_do_for / mulle_calloca_do_for: Provide a convenient iterator pattern over the allocated elements

Summary

  • Both mulle_alloca_do and mulle_calloca_do prefer stack allocation (128 bytes by default) but automatically fall back to the heap for larger requests
  • mulle_alloca_do uses mulle_malloc and leaves memory uninitialized—requires immediate writing
  • mulle_calloca_do uses mulle_calloc and guarantees zero-initialized memory via { 0 } stack declarations
  • Both automatically free heap memory when the enclosing block exits; stack memory requires no cleanup
  • Use mulle_alloca_do_realloc to dynamically grow buffers that initially fit on the stack

Frequently Asked Questions

When should I use mulle_calloca_do instead of mulle_alloca_do?

Use mulle_calloca_do when you need to read from the buffer before writing to it, or when you require zero-initialized data structures. According to the source code in src/mulle-alloca.h, this macro ensures the stack buffer is declared with { 0 } and uses mulle_calloc for heap fallbacks, guaranteeing all bytes start as zero.

What happens if the requested size exceeds the stack buffer?

Both macros automatically allocate on the heap using their respective allocators. mulle_alloca_do calls mulle_malloc while mulle_calloca_do calls mulle_calloc. The memory is automatically freed when execution leaves the for loop block via the _mulle_alloca_do_free helper, which checks whether the pointer references the stack buffer or a heap allocation.

Can I resize a buffer allocated with mulle_alloca_do?

Yes. The macro mulle_alloca_do_realloc (and its calloca counterpart) allows you to resize the buffer within the block. If the original allocation was on the stack and the new size exceeds MULLE_ALLOCA_STACKSIZE, the helper automatically copies the data to a new heap block and updates your pointer variable.

Is there a performance difference between the two macros?

For allocations that fit on the stack (≤128 bytes), performance is identical. For heap fallbacks, mulle_calloca_do incurs the additional cost of zero-initialization that mulle_calloc provides versus mulle_malloc. If you will immediately overwrite all memory, mulle_alloca_do avoids this unnecessary initialization overhead.

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 →