# How mulle_alloca_do Handles Nested Allocations and Stack Size Limits

> Discover how mulle_alloca_do manages nested allocations and stack limits. Learn about its automatic switch to heap and safe nesting features for efficient memory handling.

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

---

**The `mulle_alloca_do` macro family in mulle-c/mulle-allocator automatically switches from stack to heap allocation when requests exceed the `MULLE_ALLOCA_STACKSIZE` limit (default 128 bytes) and supports safe nesting through uniquely named hidden variables, though return shortcuts are restricted to single-level blocks.**

The `mulle-allocator` library provides temporary, block-scoped memory allocation through the `mulle_alloca_do` and `mulle_calloca_do` macros. These utilities balance performance and safety by preferring fast stack storage while transparently falling back to heap allocation when necessary, even allowing runtime promotion from stack to heap during reallocation.

## Understanding the Stack Size Limit

### Compile-Time Constant and Default Value

The library defines a compile-time threshold that determines how much data can reside on the stack. In [`src/mulle-alloca.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-alloca.h) at lines 108–110, the default limit is set:

```c
#ifndef MULLE_ALLOCA_STACKSIZE

# define MULLE_ALLOCA_STACKSIZE  128   // bytes, equivalent of double[16]

#endif

```

This 128-byte default represents a safety margin suitable for small temporary buffers. You can override this value at compile time to adjust the trade-off between stack usage and heap allocation frequency.

### Automatic Escalation to Heap Memory

When you invoke `mulle_alloca_do(type, name, count)`, the macro compares your requested size against `MULLE_ALLOCA_STACKSIZE`. As implemented in [`src/mulle-alloca.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-alloca.h) at lines 86–89, the logic uses a conditional expression:

```c
type *name = ((uintptr_t) name ## __count) <= _mulle_alloca_do_get_size_as_length(name)

               ? name ## __storage

               : mulle_malloc(sizeof(type) * (uintptr_t)(void *)name ## __count);

```

If the request fits within the limit, the pointer references the stack-allocated array `name__storage`. If it exceeds the limit, the macro calls `mulle_malloc` to create a heap buffer. This **automatic escalation** ensures that large allocations do not risk stack overflow while small allocations remain zero-cost.

## Nested Allocation Support and Safety

### Variable Name Isolation

Each `mulle_alloca_do` expansion generates a dedicated set of hidden variables using token concatenation: `name__storage`, `name__count`, `name__i`, `name__j`, and `name__k`. Because these identifiers derive from the user-provided `name` parameter, you can safely nest multiple blocks provided each uses a unique name:

```c
mulle_alloca_do(buf1, char, 64) {          // Uses buf1__storage, etc.
    // Work with buf1 on stack (64 bytes < 128 bytes)

    mulle_alloca_do(buf2, int, 32) {      // Uses buf2__storage, etc.
        // Independent allocation, may be stack or heap depending on size
    }   // buf2 cleanup runs here
}   // buf1 cleanup runs here

```

The scoping rules of C ensure that each inner block's cleanup code executes before the outer block's, preventing memory leaks or double-frees.

### Limitations with Return Shortcuts

The header explicitly warns against using the convenience return macros `_mulle_alloca_do_return` and `_mulle_alloca_do_return_void` within nested blocks. As noted at line 140 in [`src/mulle-alloca.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-alloca.h):

> "They won't magically do the right thing in **nested** `mulle_alloca_do`s."

These shortcuts are designed only for single-level blocks. In nested scenarios, you must use standard control flow:

- Use `break` to exit the innermost block, which triggers its cleanup
- Allow the block to complete normally
- Place the `return` statement after the outer block closes

This restriction exists because the return shortcuts cannot unwind multiple levels of the internal `for`-loop machinery that manages the allocations.

## Runtime Reallocation and Stack-to-Heap Promotion

Buffers initially allocated on the stack can grow beyond the original limit at runtime. The `mulle_alloca_do_realloc` macro (and its `calloca` variant) handles this promotion transparently. The implementation at lines 92–102 in [`src/mulle-alloca.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-alloca.h) detects the current storage location:

```c
if (name == name ## __storage) {

    if (_count > _mulle_alloca_do_get_size_as_length(name)) {
        name = mulle_malloc(sizeof(*name) * _count);
        memcpy(name, name ## __storage,

               ((uintptr_t) name ## __count) * sizeof(*name));

    }
} else {
    _count = _count ? _count : 1;
    name = mulle_realloc(name, _count * sizeof(*name));
}
name ## __count = (void *)_count;

```

When the new count exceeds the stack limit, the macro allocates a heap buffer, copies the existing stack contents, and updates the pointer. Subsequent reallocations use standard `mulle_realloc` since the buffer now resides on the heap. This **promotion** strategy maintains data integrity while adapting to growing memory requirements.

## Summary

- **Stack limit** is controlled by the `MULLE_ALLOCA_STACKSIZE` compile-time constant (default 128 bytes), defined in [`src/mulle-alloca.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-alloca.h).
- **Automatic escalation** occurs when allocation requests exceed the stack limit, triggering `mulle_malloc` instead of stack allocation.
- **Nested blocks** are safe when each uses a distinct variable name, as the macro generates isolated hidden identifiers via token pasting.
- **Return shortcuts** (`_mulle_alloca_do_return`) do not support nested usage; use `break` or normal block completion instead.
- **Stack-to-heap promotion** happens automatically during reallocation when the requested size surpasses the original stack capacity.
- **Cleanup** is handled by the `for`-loop structure calling `_mulle_alloca_do_free`, which frees heap buffers and ignores stack storage.

## Frequently Asked Questions

### What is the default stack size limit for mulle_alloca_do?

The default limit is **128 bytes**, defined by the `MULLE_ALLOCA_STACKSIZE` macro in [`src/mulle-alloca.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-alloca.h) (lines 108–110). You can override this at compile time by defining the macro before including the header. Requests smaller than or equal to this limit receive stack allocation; larger requests automatically use the heap.

### Can I safely nest mulle_alloca_do blocks inside each other?

Yes, provided each block uses a **unique variable name**. The macro generates hidden variables (like `name__storage` and `name__count`) using the name you provide, so distinct names prevent identifier collisions. The scoping ensures inner blocks clean up before outer blocks.

### What happens if I reallocate a stack buffer beyond MULLE_ALLOCA_STACKSIZE?

The `mulle_alloca_do_realloc` macro **promotes** the buffer from stack to heap automatically. It detects that the current pointer equals the stack storage, allocates a new heap buffer using `mulle_malloc`, copies the existing data with `memcpy`, and redirects the pointer. Future reallocations then use `mulle_realloc` on the heap buffer.

### Why do the return shortcuts fail in nested mulle_alloca_do blocks?

The `_mulle_alloca_do_return` and `_mulle_alloca_do_return_void` macros are implemented as simple flow-control wrappers that assume a single-level block structure. In nested scenarios, they cannot properly unwind the multiple `for`-loop layers that manage different buffers. The source code at line 140 in [`src/mulle-alloca.h`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-alloca.h) explicitly warns that these shortcuts "won't magically do the right thing" when nested.