# How to Create a Growable C Char Array with mulle-buffer

> Learn to create a growable C char array using mulle-buffer. Discover how to use mulle_buffer_create or MULLE_BUFFER_DATA and add data efficiently for dynamic resizing.

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

---

**Use `mulle_buffer_create()` for heap-allocated or `MULLE_BUFFER_DATA()` for stack-allocated flexible buffers, then append data with `mulle_buffer_add_string()` or `mulle_buffer_add_bytes()` to build a dynamically resizing char array.**

The **mulle-buffer** library from the `mulle-c` organization provides a lightweight, portable solution for dynamic string and byte array construction in C. Unlike fixed-size arrays, a **growable C char array** created with mulle-buffer expands automatically as you append data, eliminating manual reallocations and buffer overflow risks.

## Heap-Allocated vs. Stack-Allocated Buffers

Mulle-buffer supports two distinct allocation strategies for creating growable buffers. Both approaches implement the same underlying flexible storage mechanism found in [`src/mulle-buffer.c`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.c), but differ in where the buffer structure itself resides.

### Creating a Heap-Allocated Buffer

For dynamic lifetimes or returning buffers from functions, allocate the buffer structure on the heap using `mulle_buffer_create()`. This function, defined in [`src/mulle-buffer.c`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.c) at lines 44-51, returns a pointer to a fully initialized `struct mulle_buffer` configured with the default capacity (`MULLE_BUFFER_DEFAULT_CAPACITY`).

The underlying storage grows automatically when you invoke addition functions.

### Creating a Stack-Allocated Buffer

For local, temporary growable arrays, declare the buffer on the stack and initialize it with the `MULLE_BUFFER_DATA()` macro. Defined in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h) at lines 117-125, this macro sets the `_type` flag to `MULLE_BUFFER_IS_FLEXIBLE`, ensuring the buffer reallocates its internal storage on demand while the struct itself remains stack-allocated.

## Core Operations for Dynamic Growth

Once initialized, the buffer provides atomic operations that handle reallocation transparently. According to the implementation in [`src/mulle-buffer.c`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.c), these functions call `mulle_buffer_grow()` internally when capacity is exceeded.

### Appending Data

- **`mulle_buffer_add_byte(buf, c)`** – Appends a single character and triggers growth if the buffer is full.
- **`mulle_buffer_add_string(buf, "text")`** – Copies a C-string excluding the null terminator.
- **`mulle_buffer_add_bytes(buf, data, length)`** – Appends arbitrary binary data of specified length.

### Ensuring Capacity

When writing directly into the buffer's internal storage, use **`mulle_buffer_guarantee(buf, n)`**. This function grows the buffer if necessary to ensure `n` free bytes are available, returning a pointer to the writable area. After writing, call **`mulle_buffer_advance(buf, written)`** to update the cursor position.

For manual pre-allocation to avoid repeated reallocations during large operations, call **`mulle_buffer_grow(buf, capacity)`** before adding data.

### Extracting Content

Retrieve the current content using:

- **`mulle_buffer_get_bytes(buf)`** – Returns a raw pointer to the internal storage (not null-terminated).
- **`mulle_buffer_get_length(buf)`** – Returns the number of bytes currently stored.
- **`mulle_buffer_extract_string(buf)`** – Allocates a new null-terminated C string copy that you must free.

## Practical Code Examples

The following examples demonstrate how to create and manipulate growable char arrays using both allocation strategies.

### Example 1: Heap-Allocated Growable String

This example creates a flexible buffer on the heap, appends mixed text and binary data, then extracts the result as a C string.

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

int main(void)
{
    /* allocate a flexible buffer (default allocator) */
    struct mulle_buffer *buf = mulle_buffer_create( NULL );

    /* Append some text */
    mulle_buffer_add_string( buf, "Hello, " );
    mulle_buffer_add_string( buf, "world!" );

    /* Append raw bytes */
    const char extra[] = { ' ', 'C', '+', '+' };
    mulle_buffer_add_bytes( buf, (void *)extra, sizeof extra );

    /* Get the current length */
    size_t len = mulle_buffer_get_length( buf );   /* -> 13 */

    /* Extract a NUL-terminated C string (allocates a copy) */
    char *cstr = mulle_buffer_extract_string( buf );
    printf("%s (len=%zu)\n", cstr, len);          /* prints: Hello, world! C++ */

    /* Clean up */
    mulle_buffer_destroy( buf );
    free( cstr );   /* the string returned by extract_string must be freed */
    return 0;
}

```

### Example 2: Stack-Allocated Buffer

Use this pattern for temporary string building without heap allocation for the buffer structure itself.

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

int main(void)
{
    /* Initialise a buffer on the stack; it will grow as needed */
    struct mulle_buffer buf = MULLE_BUFFER_DATA( NULL );

    /* Use guarantee when you want to write directly into the free area */
    char *dst = mulle_buffer_guarantee( &buf, 10 );
    if (dst) {
        strcpy( dst, "StackBuf" );          /* write up to 10 bytes */
        /* advance the cursor to reflect the written data */
        mulle_buffer_advance( &buf, strlen(dst) );
    }

    /* Append more data using the high-level helpers */
    mulle_buffer_add_string( &buf, " + more" );

    /* Obtain the full content */
    char *final = mulle_buffer_extract_string( &buf );
    puts( final );        /* prints: StackBuf + more */

    /* No need to call mulle_buffer_destroy because the struct itself
       lives on the stack; just free the extracted copy. */
    free( final );
    return 0;
}

```

### Example 3: Pre-Allocating Capacity

Avoid repeated reallocations by pre-sizing the buffer for large data appends.

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

int main(void)
{
    struct mulle_buffer *buf = mulle_buffer_create( NULL );

    /* Reserve 1 MiB up front (useful for large concatenations) */
    if (mulle_buffer_grow( buf, 1024 * 1024 ) == 0) {
        /* Now all subsequent adds will not trigger further allocations */
        for (int i = 0; i < 1000; ++i)
            mulle_buffer_add_string( buf, "data;" );
    }

    printf("final size = %zu\n", mulle_buffer_get_length( buf ));
    mulle_buffer_destroy( buf );
    return 0;
}

```

## Key Implementation Files

Understanding the source architecture helps when debugging or extending buffer behavior:

- **[`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h)** – Defines the public API, including `struct mulle_buffer`, the `MULLE_BUFFER_DATA` macro, and inline helpers for length calculations.
- **[`src/mulle-buffer.c`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.c)** – Contains the core implementation of `mulle_buffer_create()`, `mulle_buffer_grow()`, and all addition functions. This file manages the `realloc` logic that enables the growable behavior.
- **`test/buffer/`** – Contains reference implementations such as [`growstress.c`](https://github.com/mulle-c/mulle-buffer/blob/main/growstress.c) and [`flexible.c`](https://github.com/mulle-c/mulle-buffer/blob/main/flexible.c) that demonstrate edge cases and performance characteristics of growable buffers.

## Summary

- **Use `mulle_buffer_create()`** to allocate a heap-based growable array that can be returned from functions or shared between scopes.
- **Use `MULLE_BUFFER_DATA()`** for zero-overhead stack allocation when building temporary strings or processing buffers locally.
- **Call `mulle_buffer_add_string()` or `mulle_buffer_add_bytes()`** to append data; the buffer automatically reallocates via `mulle_buffer_grow()` when capacity is exceeded.
- **Extract results** with `mulle_buffer_extract_string()` to obtain a null-terminated C string, or `mulle_buffer_get_bytes()` for raw pointer access.
- **Always pair** `mulle_buffer_create()` with `mulle_buffer_destroy()` to prevent memory leaks; free strings returned by `mulle_buffer_extract_string()` separately.

## Frequently Asked Questions

### What is the default initial capacity of a mulle-buffer?

According to [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h), the default capacity is defined by the `MULLE_BUFFER_DEFAULT_CAPACITY` macro. When you call `mulle_buffer_create(NULL)`, the buffer initializes with this default size and doubles as needed through `mulle_buffer_grow()`.

### Can I use mulle-buffer for binary data or only for strings?

Mulle-buffer handles arbitrary binary data safely. While functions like `mulle_buffer_add_string()` treat input as text, `mulle_buffer_add_bytes()` accepts void pointers and lengths, making the buffer suitable for any growable byte array, including serialized structures or network packets.

### How do I prevent multiple reallocations when I know the final size?

Call `mulle_buffer_grow(buf, required_size)` before adding data. As implemented in [`src/mulle-buffer.c`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.c), this function ensures the internal storage meets the requested capacity immediately, eliminating intermediate allocations during bulk operations.

### Is the buffer returned by `mulle_buffer_extract_string()` null-terminated?

Yes. Unlike `mulle_buffer_get_bytes()`, which returns a pointer to the internal buffer that may not include a terminator, `mulle_buffer_extract_string()` allocates a new buffer, copies all content, and appends a null byte. You must `free()` this returned pointer when done.