# What is mulle-buffer and How Does It Work: A Complete Guide to the C Growable Buffer Library

> Discover mulle-buffer, a C growable buffer library with automatic memory management and stack-first allocation. Learn how this efficient C library works for your projects.

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

---

**mulle-buffer is a lightweight C library that implements a growable byte buffer with automatic memory management, stack-first allocation, and a macro-based lifecycle that ensures cleanup.**

The mulle-c/mulle-buffer repository provides a dynamic memory container and stream-like writer for C applications. Unlike standard C strings or fixed-size arrays, mulle-buffer handles automatic resizing and memory cleanup through a sophisticated macro system and allocator abstraction.

## Core Architecture of mulle-buffer

At its foundation, mulle-buffer wraps a thin abstraction around raw memory management. The library distinguishes between two operational modes that determine how memory growth behaves.

### Underlying Data Structures

The buffer implementation centers on `struct mulle_buffer`, defined in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h) at lines 70-74. This structure extends `struct mulle__buffer` with a pointer to a memory allocator:

```c
struct mulle_buffer {
    struct mulle__buffer _buffer;
    struct mulle_allocator *_allocator;
};

```

According to the mulle-c/mulle-buffer source code, the `mulle__buffer` base structure manages the actual storage pointers, capacity, and length, while the wrapper adds allocator flexibility.

### Flexible vs. Inflexible Buffer Modes

mulle-buffer operates in two distinct modes depending on whether the storage region can expand:

- **Flexible buffers** grow dynamically by allocating new memory when capacity is exceeded. Create these using `mulle_buffer_do` or `mulle_buffer_do_flexible` macros.
- **Inflexible buffers** remain confined to a fixed storage region, discarding excess data rather than growing. Create these using `mulle_buffer_do_inflexible`.

The mode is determined by flags set during initialization, specifically `MULLE_BUFFER_IS_FLEXIBLE`, as implemented in the macro definitions within [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h).

## Stack-First Allocation and Automatic Cleanup

The library optimizes for performance through stack-first allocation and eliminates memory leaks via automatic cleanup mechanisms.

### The mulle_buffer_do Macro Pattern

The `mulle_buffer_do` macro reserves a small block on the stack (defaulting to 96 bytes) and only falls back to heap allocation when data exceeds that size. According to the source code around line 2100 in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h), the macro builds a temporary array using `__alloca` and passes it to `MULLE_BUFFER_FLEXIBLE_DATA`:

```c
mulle_buffer_do( buf )
{
    mulle_buffer_add_string( buf, "Hello, World" );
}  // automatic cleanup occurs here

```

This approach minimizes heap fragmentation for small, short-lived buffers common in string formatting and message construction.

### Guaranteed Cleanup with For-Loop Tricks

The `*_do*` macros embed a `for` loop that guarantees `mulle_buffer_done(&storage)` executes when the block ends, even if a `break` statement occurs. The cleanup happens in the loop-increment expression:

```c
name ## __i = (mulle_buffer_done(&name ## __storage), (void *)0x1)

```

This pattern ensures that heap storage is freed and internal state is reset without requiring explicit cleanup code in every exit path.

## Memory Management and Allocator Abstraction

All memory operations route through a configurable allocator interface, allowing integration with custom memory pools or tracking systems.

### Allocator Integration

By default, mulle-buffer uses `mulle_default_allocator`, but every buffer carries a `struct mulle_allocator` pointer. Functions like `mulle_buffer_get_allocator` retrieve the current allocator, while allocation and growth use `_mulle__buffer_grow` with that allocator, as seen in [`src/mulle-buffer.c`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.c) at line 59.

This abstraction allows applications to substitute custom allocators for specialized memory management:

```c
extern struct mulle_allocator my_allocator;

mulle_buffer_do_allocator( buf, &my_allocator )
{
    mulle_buffer_add_byte( buf, 0xFF );
}

```

### Growth Strategy

When writing exceeds current capacity, `_mulle__buffer_grow` requests a larger block from the allocator. The growth strategy doubles capacity (or similar exponential growth) to maintain amortized O(1) append operations, consistent with standard dynamic array implementations.

## Writing and Reading Operations

The library provides a high-level API that forwards to low-level implementations, separating user convenience from core logic.

### High-Level API Functions

Inline helpers such as `mulle_buffer_add_byte`, `mulle_buffer_add_string`, and `mulle_buffer_guarantee` reside in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h) (lines 1159-1170). These functions perform safety checks like `mulle_buffer_assert_writeable` before calling low-level routines:

- `mulle_buffer_add_byte` → `_mulle__buffer_add_byte`
- `mulle_buffer_add_string` → `_mulle__buffer_add_bytes`

Reading accessors include `mulle_buffer_get_bytes`, `mulle_buffer_get_string`, and `mulle_buffer_get_length`, which return raw pointers or `mulle_data` structures pointing into current storage.

### Low-Level Implementation Details

The core logic in [`src/mulle-buffer.c`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.c) handles actual memory manipulation. Functions like `_mulle__buffer_add_byte` check capacity and trigger growth when necessary, while `_mulle__buffer_done` performs final cleanup by freeing heap storage if the buffer grew beyond its initial stack allocation.

## Practical Usage Examples

The following patterns demonstrate typical mulle-buffer use cases, from simple string building to custom memory management.

### Simple Dynamic String Building

Use `mulle_buffer_do` for ad-hoc C-string assembly inside functions:

```c
void demo_simple( void )
{
   mulle_buffer_do( buf )
   {
      mulle_buffer_add_string( buf, "Hello, " );
      mulle_buffer_add_string( buf, "mulle-buffer!" );
      printf( "%s\n", mulle_buffer_get_string( buf ) );
   }
}

```

The macro expands to a `for` loop that guarantees `mulle_buffer_done` is called automatically.

### Custom Allocator Integration

For applications requiring specialized memory tracking, pass a custom allocator:

```c
extern struct mulle_allocator my_allocator;

void demo_custom_alloc( void )
{
   mulle_buffer_do_allocator( buf, &my_allocator )
   {
      for( int i = 0; i < 256; ++i )
         mulle_buffer_add_byte( buf, (unsigned char)i );

      printf( "size = %zu\n", mulle_buffer_get_length( buf ) );
   }
}

```

### Fixed-Size Stack Buffers

Use `mulle_buffer_do_inflexible` to treat existing memory as a buffer without copying or growth:

```c
void demo_inflexible( void )
{
   char storage[12] = "VfL_";
   mulle_buffer_do_inflexible( buf, storage, sizeof storage )
   {
      mulle_buffer_add_string( buf, "Bochum" );
      printf( "%s\n", mulle_buffer_get_string( buf ) );
   }
}

```

Because the buffer is inflexible, writing beyond the supplied storage simply discards excess data rather than reallocating.

### Extracting Heap-Allocated Strings

The `mulle_buffer_do_string` macro (defined around line 2064 in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h)) creates a buffer, runs user code, extracts a C-string, and frees the buffer automatically:

```c
void demo_extract_string( void )
{
   char *s;

   mulle_buffer_do_string( buf, NULL, s )
   {
      for( int i = 0; i < 10; ++i )
         mulle_buffer_add_byte( buf, 'a' + i );
      mulle_buffer_add_string( buf, "end" );
   }

   printf( "%s\n", s );
   mulle_free( s );
}

```

The macro destroys the buffer after extraction, leaving `s` pointing to a malloc-ed C-string that the caller must eventually free.

## Summary

- **mulle-buffer** is a growable byte buffer library for C that combines stack-first allocation with automatic heap fallback.
- The library uses **flexible and inflexible modes** to control whether buffers grow dynamically or remain fixed in size.
- **Automatic cleanup** is guaranteed through `for` loop macro tricks that call `mulle_buffer_done` on all exit paths.
- All memory operations route through an **allocator abstraction** (`struct mulle_allocator`), enabling custom memory management strategies.
- The API separates **high-level inline helpers** from **low-level core functions**, with the former residing in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h) and the latter in [`src/mulle-buffer.c`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.c).
- **Stack-first allocation** (default 96 bytes) optimizes performance for small, temporary buffers while ensuring seamless growth for larger payloads.

## Frequently Asked Questions

### What is mulle-buffer used for?

mulle-buffer serves as a dynamic memory container and stream-like writer for C applications. It excels at ad-hoc string building, binary payload construction, and read-only views of existing memory without copying. The library is particularly useful when you need NSMutableData-like functionality in C without manual memory management overhead.

### How does mulle-buffer handle memory allocation?

mulle-buffer implements a stack-first strategy where small buffers (96 bytes by default) reside on the stack via `__alloca`, falling back to heap allocation only when data exceeds this capacity. All allocation and deallocation route through a configurable `struct mulle_allocator` pointer, defaulting to `mulle_default_allocator`. When flexible buffers grow, `_mulle__buffer_grow` requests larger blocks from the current allocator.

### What is the difference between flexible and inflexible buffers?

**Flexible buffers** (created with `mulle_buffer_do` or `mulle_buffer_do_flexible`) automatically grow by allocating new memory when capacity is exceeded, suitable for unknown or variable data sizes. **Inflexible buffers** (created with `mulle_buffer_do_inflexible`) remain confined to a fixed storage region provided at initialization, discarding excess data rather than reallocating, which is useful for working with pre-allocated memory or safety-constrained contexts.

### How does automatic cleanup work in mulle-buffer?

The `mulle_buffer_do` family of macros embeds cleanup logic in a `for` loop's increment expression. This ensures `mulle_buffer_done(&storage)` executes when the block terminates, including on `break` statements or exceptions. The cleanup frees any heap storage that was allocated during growth and resets the internal buffer state, while stack memory disappears automatically when the function returns.