# mulle_buffer_do_flexible and mulle_buffer_do_inflexible: Scoped Buffer Management in C

> Discover mulle_buffer_do_flexible and mulle_buffer_do_inflexible for scoped C buffer management. Learn how to create stack-allocated buffers with automatic cleanup and optional heap growth for flexible or fixed-size storage.

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

---

**Both macros create stack-allocated `struct mulle_buffer` instances with automatic cleanup, where `mulle_buffer_do_flexible` allows dynamic heap growth while `mulle_buffer_do_inflexible` enforces fixed-size stack-only storage.**

The mulle-c/mulle-buffer library provides these convenience macros to simplify buffer management in C without manual initialization and teardown. They implement a **RAII-style pattern** using stack allocation and guaranteed cleanup, making them ideal for performance-critical code paths where deterministic memory behavior matters.

## What These Macros Do

`mulle_buffer_do_flexible` and `mulle_buffer_do_inflexible` are **scoped helper macros** defined in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h) that automate the lifecycle of a `mulle_buffer`. Both wrap the buffer creation, initialization with user-supplied storage, and mandatory cleanup into a single control structure that executes when the surrounding scope exits.

The implementation uses a sophisticated `for` loop technique (lines 2164-2175 for the flexible variant and lines 2224-2235 for the inflexible variant in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h)) to ensure `mulle_buffer_done()` is called exactly once, even when using `break` or `continue` statements.

## mulle_buffer_do_flexible: Dynamic Growth on Demand

Use `mulle_buffer_do_flexible` when you want **stack-first allocation with heap fallback**. The macro initializes the buffer with provided stack storage, but if writes exceed the initial capacity, the buffer automatically reallocates using the default allocator (`malloc`/`realloc`).

This approach eliminates unnecessary heap allocations for small payloads while accommodating unexpected growth.

```c
#include <mulle-buffer/mulle-buffer.h>
#include <stdio.h>

static void example_flexible(const char *txt)
{
    char tmp[8];  // Small stack buffer to avoid initial malloc
    mulle_buffer_do_flexible(buf, tmp, sizeof(tmp))
    {
        /* Buffer grows automatically if txt exceeds 8 bytes */
        mulle_buffer_add_c_string(buf, txt);
        printf("%s\n", mulle_buffer_get_string(buf));
    }
    /* mulle_buffer_done called automatically here */
}

```

As demonstrated in [`test/buffer/do-flexible.c`](https://github.com/mulle-c/mulle-buffer/blob/main/test/buffer/do-flexible.c), this pattern suits small-to-medium workloads where the final size is unknown but typically small.

## mulle_buffer_do_inflexible: Strictly Fixed Storage

Use `mulle_buffer_do_inflexible` when you must **guarantee zero heap allocation**. The buffer operates strictly within the provided storage block. Any write operation that would exceed the buffer capacity is silently truncated or generates an overflow error—no dynamic allocation ever occurs.

This variant is essential for embedded systems, real-time applications, or safety-critical code where heap fragmentation and allocation latency are unacceptable.

```c
#include <mulle-buffer/mulle-buffer.h>
#include <stdio.h>

static void example_inflexible(const char *txt)
{
    char tmp[8];  // Fixed storage, immutable capacity
    mulle_buffer_do_inflexible(buf, tmp, sizeof(tmp))
    {
        /* Excess bytes are truncated; no malloc ever called */
        mulle_buffer_add_c_string(buf, txt);
        printf("%s\n", mulle_buffer_get_string(buf));
    }
}

```

The test file [`test/buffer/do-inflexible.c`](https://github.com/mulle-c/mulle-buffer/blob/main/test/buffer/do-inflexible.c) validates this behavior for deterministic memory environments.

## Implementation Mechanics

Both macros expand to a **dual `for` loop structure** that guarantees cleanup:

1. The **outer loop** creates the buffer on the stack and initializes it with your storage pointer and length. The loop condition ensures the body runs exactly once, while the increment section calls `mulle_buffer_done(&name##__storage)` when the scope exits.

2. The **inner loop** provides break-protection, allowing single-statement bodies without extra braces while ensuring `continue` statements do not skip the cleanup phase.

The flexible variant configures the buffer with allocator callbacks that permit reallocation, while the inflexible variant sets these to `NULL`, enforcing the fixed-size constraint.

## Summary

- **mulle_buffer_do_flexible** combines stack performance with heap flexibility, growing via `malloc`/`realloc` when initial storage proves insufficient.
- **mulle_buffer_do_inflexible** guarantees zero heap allocation by truncating writes that exceed the static buffer size.
- Both macros handle `mulle_buffer` lifecycle management automatically through scoped cleanup in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h).
- Choose flexible for general-purpose string building with unknown sizes; choose inflexible for embedded drivers, packet buffers, or deterministic real-time systems.

## Frequently Asked Questions

### What is the main difference between mulle_buffer_do_flexible and mulle_buffer_do_inflexible?

**mulle_buffer_do_flexible** allows the buffer to allocate additional memory from the heap when the initial stack storage fills up, while **mulle_buffer_do_inflexible** strictly prohibits any heap allocation and truncates data that exceeds the provided buffer size. The choice depends on whether your application requires dynamic growth or deterministic memory constraints.

### How do these macros ensure the buffer is cleaned up properly?

Both macros implement a **scoped destructor pattern** using a `for` loop that initializes the buffer in the setup clause and calls `mulle_buffer_done()` in the increment clause. This guarantees cleanup executes exactly once when the surrounding block exits, even if the code uses `break`, `continue`, or `return` statements.

### Can I use break or continue statements inside these macro blocks?

Yes. The macro structure includes an inner `for` loop specifically to protect against `break` statements skipping the cleanup phase. When you write `break` inside the macro block, you exit the inner loop but remain within the outer loop's scope, ensuring `mulle_buffer_done()` still executes before the scope terminates.

### When should I prefer mulle_buffer_do_inflexible over the flexible variant?

Prefer **mulle_buffer_do_inflexible** for embedded environments, interrupt handlers, or real-time systems where heap allocation is forbidden or introduces unacceptable latency. It is also appropriate when building fixed-size protocol packets or when operating under strict memory budgets where fragmentation risks must be eliminated entirely.