# How mulle-buffer Uses Stack and Heap Memory for Dynamic Buffers in C

> Discover how mulle-buffer efficiently manages dynamic buffers in C by utilizing stack and heap memory with alloca and mulle_allocator for optimal performance.

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

---

**mulle-buffer initializes storage on the stack using alloca or user-provided static arrays, then transparently migrates to heap allocation via the mulle_allocator abstraction only when the buffer grows beyond its initial capacity.**

The mulle-c/mulle-buffer library provides a dynamic byte buffer implementation for C that uniquely straddles stack and heap memory. Unlike standard heap-only solutions, it defaults to stack allocation for small data and automatically promotes to the heap when needed, minimizing allocation overhead for typical use cases while maintaining flexibility for larger payloads.

## Stack-First Allocation Strategy

### Default Stack Buffers with mulle_buffer_do

The primary entry point `mulle_buffer_do` allocates initial storage on the stack using `alloca`. In [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h), the default capacity is defined as `MULLE_BUFFER_DEFAULT_CAPACITY` (approximately 96 bytes) at lines 78-81.

When you write `mulle_buffer_do( buf ) { … }`, the macro expands to a block that declares a temporary `struct mulle_buffer` named `buf__storage` and calls:

```c
mulle_buffer_init( &buf__storage, MULLE_BUFFER_DEFAULT_CAPACITY, NULL );

```

This initialization appears around line 2100 in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h). The `NULL` allocator argument instructs the buffer to use the default allocator for future heap growth, but the initial storage resides entirely in the `alloca`-allocated region on the stack.

### Explicit Static Storage Options

For larger or custom stack allocations, the library provides `mulle_buffer_do_flexible` and `mulle_buffer_do_inflexible`. These macros invoke `mulle_buffer_init_with_static_bytes` (implemented inline around line 447 in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h)), which stores a pointer to your user-supplied array in the internal `_storage` field.

- **Flexible mode**: The buffer may grow to the heap if it exhausts the static array.
- **Inflexible mode**: The buffer treats the static array as a hard limit and reports overflow if exceeded.

The repository includes SVG diagrams in the `pix/` directory (`mulle-buffer-alloca.svg` and `mulle-buffer-malloc.svg`) that visually illustrate these stack-backed versus heap-backed memory layouts.

## Automatic Heap Migration

### The Growth Trigger

The transition from stack to heap occurs automatically the first time the buffer cannot satisfy a write request within its current storage. All public `mulle_buffer_add_*` functions check `mulle_buffer_assert_writeable` before forwarding to private `_mulle__buffer_*` helpers in [`src/mulle--buffer.c`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle--buffer.c).

When growth is required, `_mulle__buffer_grow` (lines 345-347) calculates a new size and reallocates:

```c
new_size = _mulle__buffer_get_new_allocation_length( buffer, growth );
p = mulle_allocator_realloc( allocator, malloc_block, new_size );

```

The `mulle_allocator_realloc` call allocates a new heap block, copies existing data from the stack region, and updates the buffer's internal pointers.

### Heap-Only Creation

For scenarios requiring immediate heap allocation, `mulle_buffer_create` (lines 384-388 in [`src/mulle--buffer.c`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle--buffer.c)) allocates the `struct mulle__buffer` itself on the heap using `mulle_allocator_malloc`. Subsequent growth operations continue using the specified allocator, bypassing any stack storage phase.

## Practical Usage Patterns

### Simple Stack-Backed Buffer (Default 96 Bytes)

```c
#include "mulle-buffer.h"

void demo_default(void)
{
    mulle_buffer_do( buf )
    {
        /* The buffer lives on the stack (alloca). */
        mulle_buffer_add_string( &buf, "Hello, " );
        mulle_buffer_add_string( &buf, "world!" );

        printf("%s\n", mulle_buffer_get_string( &buf ));
    }   /* buf is automatically destroyed here */
}

```

This pattern uses `mulle_buffer_do` to create a stack-backed buffer that automatically falls back to heap allocation if the combined string exceeds 96 bytes.

### Flexible Stack Storage with Heap Fallback

```c
void demo_flexible(void)
{
    char stack[256];                     /* 256 bytes on the stack */
    mulle_buffer_do_flexible( buf, stack, sizeof(stack) )
    {
        /* Buffer starts on the stack. If >256 bytes are added it will realloc. */
        for (int i = 0; i < 300; ++i)
            mulle_buffer_add_byte( &buf, 'x' );

        printf("len=%zu\n", mulle_buffer_get_length( &buf ));   /* 300 */
    }
}

```

The first 256 bytes remain on the stack; the remaining 44 bytes trigger a heap allocation via the default allocator.

### Inflexible Stack Buffer (No Heap Fallback)

```c
void demo_inflexible(void)
{
    char stack[8];                       /* 7 bytes + NUL */
    mulle_buffer_do_inflexible( buf, stack, sizeof(stack) )
    {
        mulle_buffer_add_string( &buf, "VfL_" );   /* fits */
        mulle_buffer_add_string( &buf, "Bochum" );/* overflow → ignored */
        printf("%s\n", mulle_buffer_get_string( &buf ));   /* prints "VfL_" */
    }
}

```

Because the buffer is **inflexible**, attempts to exceed the static storage abort the write rather than allocating heap memory.

### Explicit Heap-Only Buffer

```c
void demo_heap(void)
{
    struct mulle_buffer *buf = mulle_buffer_create( NULL );   /* malloc */
    mulle_buffer_add_string( buf, "Allocated on the heap" );
    puts( mulle_buffer_get_string( buf ) );
    mulle_buffer_destroy( buf );    /* free */
}

```

`mulle_buffer_create` allocates the buffer structure itself on the heap, with all subsequent growth using the allocator directly.

## Summary

- **mulle-buffer** defaults to stack allocation via `alloca` (96 bytes) through the `mulle_buffer_do` macro, minimizing heap fragmentation for small data.
- **Explicit static storage** macros (`mulle_buffer_do_flexible` and `mulle_buffer_do_inflexible`) allow custom stack arrays with optional or prohibited heap growth.
- **Automatic promotion** to heap memory occurs in `_mulle__buffer_grow` (defined in [`src/mulle--buffer.c`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle--buffer.c)) when stack capacity is exhausted, using the `mulle_allocator` abstraction for portability.
- **Heap-only mode** is available via `mulle_buffer_create` for scenarios where stack allocation is undesirable or impossible.

## Frequently Asked Questions

### When does mulle-buffer switch from stack to heap memory?

The transition occurs the first time a write operation exceeds the available stack storage. The `mulle_buffer_add_*` family checks capacity through `mulle_buffer_assert_writeable` and invokes `_mulle__buffer_grow` in [`src/mulle--buffer.c`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle--buffer.c), which calls `mulle_allocator_realloc` to move the buffer to a heap-allocated block.

### What is the default stack size when using mulle_buffer_do?

The default capacity is defined by `MULLE_BUFFER_DEFAULT_CAPACITY` in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h) (approximately 96 bytes). This value represents the initial `alloca` size before any heap fallback occurs.

### Can I prevent a mulle-buffer from ever allocating on the heap?

Yes. Use the `mulle_buffer_do_inflexible` macro with a user-provided static array. This sets the buffer to **inflexible** mode, causing write operations to abort rather than trigger heap allocation via `_mulle__buffer_grow` when capacity is exceeded.

### How do I create a buffer that starts immediately on the heap?

Call `mulle_buffer_create( NULL )` (or pass a specific allocator). This function, implemented in [`src/mulle--buffer.c`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle--buffer.c), allocates the `struct mulle__buffer` itself using `mulle_allocator_malloc`, bypassing the stack allocation phase entirely. You must later call `mulle_buffer_destroy` to free this memory.