Understanding Buffer Modes in mulle-buffer: Core Types, Access Controls, and Seek Behavior

mulle-buffer provides three orthogonal buffer mode families—core type modes (flexible, inflexible, flushable), access control modes (read-only, write-only, text/binary), and seek positioning modes—that determine memory allocation strategy, I/O permissions, and navigation behavior in C applications.

The mulle-c/mulle-buffer library implements a dynamic memory buffer system where buffer modes in mulle-buffer control every aspect of data storage and retrieval. These modes are defined across multiple header files and stored in a compact bitfield format, allowing developers to configure fixed-size stack buffers, growable heap storage, or flushable I/O sinks with precise access controls.

Core Buffer Type Modes

The fundamental buffer behavior is determined by the core type enumeration defined in src/mulle--buffer.h. These values occupy the low two bits of the buffer's internal _type field and control memory allocation flexibility.

Flexible Mode (MULLE_BUFFER_IS_FLEXIBLE)

Flexible mode (value 0) represents the default behavior where the buffer dynamically allocates and reallocates memory as needed to accommodate writes. When using MULLE_BUFFER_DATA(NULL), you create a flexible buffer that grows automatically with the default allocator. This mode is ideal for general-purpose string building or data accumulation where the final size is unknown.

Inflexible Mode (MULLE_BUFFER_IS_INFLEXIBLE)

Inflexible mode (value 1) creates a fixed-size buffer backed by pre-allocated memory, typically stack-allocated arrays. Declared using MULLE_BUFFER_INFLEXIBLE_DATA(stack, sizeof stack), this mode will trigger assertions if write operations exceed the allocated capacity. Use this for performance-critical scenarios where dynamic allocation must be avoided.

Flushable Mode (MULLE_BUFFER_IS_FLUSHABLE)

Flushable mode (value 2) enables buffers that can write their contents to an underlying sink through the flush operation. Defined in src/mulle-flushablebuffer.h, this mode is used with struct mulle_flushablebuffer to implement streaming I/O where accumulated data periodically empties into files or network sockets rather than remaining in memory indefinitely.

Sprintf Inflexible Mode (MULLE_BUFFER_IS_SPRINTF_INFLEXIBLE)

Sprintf inflexible mode (value 3) provides a specialized internal buffer type used specifically for sprintf operations within the library, offering optimized behavior for formatted string output into fixed-size targets.

Access Control and Data Kind Modes

Beyond core types, src/mulle-buffer.h defines higher-order bit flags (stored in bits 6-8 of the _type field) that restrict access patterns and specify data interpretation for stdio helpers.

Read-Only and Write-Only Modes

The library enforces access restrictions through bitwise flags:

  • MULLE_BUFFER_IS_READONLY (0x40): Marks the buffer as read-only, causing assertions on any write attempt
  • MULLE_BUFFER_IS_WRITEONLY (0x80): Marks the buffer as write-only, causing assertions on any read attempt

Use the inline helpers mulle_buffer_set_readonly() and mulle_buffer_set_writeonly() to apply these restrictions after initialization. The helper mulle_buffer_is_readonly() tests these bits before allowing write operations.

Text vs Binary Data Interpretation

The MULLE_BUFFER_IS_TEXT (0x100) and MULLE_BUFFER_IS_BINARY (0x0) flags control how the stdio integration layer handles line endings and data transformation. Text mode enables newline conversion behaviors, while binary mode (the default) preserves exact byte sequences.

Seek Modes for Buffer Positioning

Buffer navigation uses standard seek constants defined in src/mulle--buffer.h, mirroring the classic C stdio seek behavior:

  • MULLE_BUFFER_SEEK_SET (0): Position relative to the start of the buffer
  • MULLE_BUFFER_SEEK_CUR (1): Position relative to the current offset
  • MULLE_BUFFER_SEEK_END (2): Position relative to the end of the buffer

The functions mulle_buffer_set_seek() and mulle_buffer_get_seek() manipulate the cursor position using these constants, while mulle_buffer_advance() moves the cursor forward by a specified number of bytes.

How Buffer Modes Are Stored Internally

All mode flags coexist in the buffer structure's _type field, a 32-bit unsigned integer. The bit layout follows this architecture:

  • Bits 0-1: Core buffer type (flexible, inflexible, flushable, sprintf-inflexible)
  • Bits 6-8: Access and data kind flags (read-only, write-only, text/binary)
  • Remaining bits: Reserved for future expansion

This compact representation allows the library to test multiple mode properties simultaneously using bitwise operations, minimizing overhead during high-performance buffer operations.

Practical Examples of Buffer Mode Combinations

The following examples demonstrate how to initialize and configure different buffer modes in mulle-buffer:

/* 1. Flexible, read-write buffer (default) */
struct mulle_buffer buf = MULLE_BUFFER_DATA(NULL);
mulle_buffer_write(&buf, "hello", 5);

/* 2. Inflexible (fixed-size) buffer on the stack */
unsigned char stack[64];
struct mulle_buffer fixed = MULLE_BUFFER_INFLEXIBLE_DATA(stack, sizeof(stack));
mulle_buffer_write(&fixed, "fixed", 5);   // asserts if overflow occurs

/* 3. Mark a buffer as read-only – further writes will assert */
mulle_buffer_set_readonly(&buf);

/* 4. Seek within a buffer */
mulle_buffer_set_seek(&buf, 2, MULLE_BUFFER_SEEK_SET);
char *ptr = mulle_buffer_advance(&buf, 1);

/* 5. Flushable, write-only buffer for streaming output */
struct mulle_flushablebuffer fbuf;
mulle_flushablebuffer_init(&fbuf, NULL);
mulle_buffer_set_writeonly(&fbuf.buffer);
mulle_buffer_write(&fbuf.buffer, "data", 4);
mulle_flushablebuffer_flush(&fbuf);

Summary

  • mulle-buffer implements three orthogonal mode families defined across src/mulle--buffer.h and src/mulle-buffer.h
  • Core types control memory allocation: MULLE_BUFFER_IS_FLEXIBLE (0) for dynamic growth, MULLE_BUFFER_IS_INFLEXIBLE (1) for fixed-size, MULLE_BUFFER_IS_FLUSHABLE (2) for streamable sinks
  • Access flags enforce I/O restrictions: MULLE_BUFFER_IS_READONLY (0x40) and MULLE_BUFFER_IS_WRITEONLY (0x80) prevent unauthorized operations
  • Data kind flags specify interpretation: MULLE_BUFFER_IS_TEXT (0x100) versus binary mode (0x0) for stdio handling
  • Seek modes provide navigation: MULLE_BUFFER_SEEK_SET (0), MULLE_BUFFER_SEEK_CUR (1), and MULLE_BUFFER_SEEK_END (2)
  • All modes compress into the _type field using specific bitmasks for efficient runtime testing

Frequently Asked Questions

What is the difference between flexible and inflexible buffer modes in mulle-buffer?

Flexible mode (MULLE_BUFFER_IS_FLEXIBLE) allows the buffer to dynamically allocate and reallocate memory as data grows, suitable for unknown or variable data sizes. Inflexible mode (MULLE_BUFFER_IS_INFLEXIBLE) operates on fixed pre-allocated storage (typically stack memory) and triggers assertions when writes exceed capacity, offering predictable memory usage for safety-critical applications.

How do I make a mulle-buffer read-only?

Call mulle_buffer_set_readonly() on an initialized buffer to set the MULLE_BUFFER_IS_READONLY flag (0x40). According to the implementation in src/mulle-buffer.h, subsequent write attempts will assert or fail silently depending on build configuration, protecting the buffer content from modification while allowing reads to proceed normally.

What does the flushable buffer mode do?

Flushable mode (MULLE_BUFFER_IS_FLUSHABLE) enables buffers to transfer accumulated data to an underlying sink (such as a file descriptor or network socket) rather than retaining it in memory. As implemented in src/mulle-flushablebuffer.h, calling mulle_flushablebuffer_flush() writes the current contents to the configured destination and resets the buffer cursor, making this mode ideal for logging systems or streaming data processors.

How are buffer modes stored internally in mulle-buffer?

All modes are stored in the _type field of the struct mulle_buffer as a 32-bit integer bitmask. The core buffer type occupies bits 0-1 (values 0-3), while access control and data kind flags occupy higher-order bits (e.g., bit 6 for read-only, bit 7 for write-only, bit 8 for text mode). Inline helper functions like mulle_buffer_is_readonly() perform bitwise AND operations against these masks to test current modes efficiently.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →