# How to Use the mulle_buffer_do Macro for Flexible Buffer Creation in C

> Master flexible buffer creation with mulle_buffer_do. Learn how this C macro creates stack buffers that automatically grow to the heap and ensure cleanup for robust memory management.

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

---

**The `mulle_buffer_do_flexible` macro creates a scoped buffer on the stack that automatically grows to the heap when capacity is exceeded and guarantees cleanup via `mulle_buffer_done` when the block exits, even if you break early.**

The `mulle_buffer_do` macro family in the **mulle-c/mulle-buffer** repository provides a deterministic, scope-bound approach to dynamic memory management in C. These macros let you create flexible buffers that start as stack allocations for optimal performance but transparently escalate to heap storage using a specified allocator when data exceeds the initial capacity.

## What Makes a Buffer "Flexible"

A flexible buffer begins life with `MULLE_BUFFER_FLEXIBLE_DATA`, using a fixed-size stack allocation of `MULLE_BUFFER_DEFAULT_CAPACITY` bytes. If your write operations exceed this initial storage, the buffer automatically allocates memory from the provided allocator (or the default allocator if `NULL` is passed). This hybrid approach delivers stack-speed for small payloads while handling arbitrarily large data without manual reallocation logic.

## Macro Anatomy and Expansion

When you invoke `mulle_buffer_do_flexible( name, allocator )`, the preprocessor expands this into a nested `for`-loop structure that declares three critical local variables:

- **`name ## __storage`** – The actual `struct mulle_buffer` instance initialized with flexible storage semantics.

- **`*name`** – A pointer to the storage that you manipulate inside the block (e.g., `mulle_buffer_add_string( name, "data" )`).
- **`*name ## __i`** – A sentinel integer ensuring the cleanup phase runs exactly once after the body completes.

The outer loop condition `!name ## __i` ensures that `mulle_buffer_done( &name ## __storage )` executes exactly once when the scope terminates, releasing any heap memory that was allocated during growth.

## Safety Guarantees and Control Flow

The macro's design guarantees cleanup execution even if you exit the block early using `break`. Because the cleanup code resides in the increment section of the outer `for` loop, it runs regardless of how the body terminates, provided you do not use `return`. 

If you need to return from the enclosing function inside the buffer block, use the companion macro `mulle_buffer_return` instead of a bare `return` statement. A standard `return` bypasses the macro's cleanup logic, potentially leaking heap memory.

## Practical Code Examples

The following examples demonstrate common usage patterns for flexible buffer creation in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h).

### Basic String Construction with Default Allocator

```c
{
    char *result;

    mulle_buffer_do_flexible( buf, NULL )          // NULL → use default allocator
    {
        mulle_buffer_add_string( buf, "Hello, " );
        mulle_buffer_add_string( buf, "world!" );

        if ( some_condition )
            break;                                 // Cleanup still runs
    }
    result = mulle_buffer_extract_string( &buf );  // Extract before scope closes
    printf("%s\n", result);
    mulle_free( result );
}

```

### Using a Custom Allocator

```c
{
    struct mulle_allocator *alloc = &mulle_default_allocator;
    char *out;

    mulle_buffer_do_flexible( buf, alloc )
    {
        mulle_buffer_add_uint32( buf, 0xDEADBEEF );
        /* Additional writes... */
    }
    out = mulle_buffer_extract_string( &buf );
    puts( out );
    mulle_free( out );
}

```

### Pre-filled Flexible Buffer

For buffers that start with existing data but may need to grow:

```c
{
    unsigned char data[32] = "pre-filled payload";
    size_t len = strlen( (char *)data );

    mulle_buffer_do_flexible_filled( buf, data, len )
    {
        mulle_buffer_add_string( buf, " – more data" );
    }
    /* Buffer can still grow beyond the original 32 bytes if needed */
}

```

## Source Code Reference

The macro definitions reside in **[`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h)**:

- General `mulle_buffer_do` infrastructure begins at [line 2100](https://github.com/mulle-c/mulle-buffer/blob/master/src/mulle-buffer.h#L2100)
- The flexible variant `mulle_buffer_do_flexible` starts at [line 2164](https://github.com/mulle-c/mulle-buffer/blob/master/src/mulle-buffer.h#L2164)

Additional implementation details appear in [`src/mulle--buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle--buffer.h), which contains the low-level `_mulle__buffer_*` operations invoked by the macros. For reference test cases, see [`test/buffer/flexible.c`](https://github.com/mulle-c/mulle-buffer/blob/main/test/buffer/flexible.c).

## Summary

- **Stack-to-heap escalation**: Buffers begin with `MULLE_BUFFER_DEFAULT_CAPACITY` bytes on the stack and grow via the specified allocator when full.
- **Automatic cleanup**: The macro expands to `for`-loops that guarantee `mulle_buffer_done` executes exactly once, even on `break`.
- **Scoped pointers**: The macro provides a local pointer `name` to the `struct mulle_buffer` instance for type-safe operations.
- **Safe extraction**: Call `mulle_buffer_extract_string` or similar inside the block to transfer ownership of data to an outer scope variable before automatic destruction.
- **Return handling**: Use `mulle_buffer_return` instead of bare `return` to ensure cleanup when exiting the function from within the block.

## Frequently Asked Questions

### What happens if I write more data than the initial stack capacity?

The buffer automatically reallocates using the allocator you provided (or the default allocator if `NULL`). The growth is transparent to your code; you simply continue calling `mulle_buffer_add_*` functions without checking capacity.

### Can I use `return` inside the `mulle_buffer_do_flexible` block?

A bare `return` bypasses the macro's cleanup logic and leaks any heap memory the buffer allocated. Use the companion macro `mulle_buffer_return` instead, which performs cleanup before returning from the function.

### How do I retrieve data after the buffer scope ends?

You must extract data before the closing brace of the block. Functions like `mulle_buffer_extract_string` return heap-allocated copies of the buffer contents. Store this pointer in a variable declared outside the macro block, then free it manually (e.g., with `mulle_free`) when done.

### What is the difference between `mulle_buffer_do_flexible` and `mulle_buffer_do_flexible_filled`?

`mulle_buffer_do_flexible` starts with empty storage, while `mulle_buffer_do_flexible_filled` accepts initial data and length parameters to pre-populate the buffer. Both variants maintain the ability to grow beyond their initial allocation if subsequent writes exceed capacity.