Can mulle-buffer Be Used as a Stream? C Stream Implementation Guide
Yes, mulle-buffer implements a byte-stream abstraction with read/write cursor mechanics, seek support, and automatic flushing capabilities that allow it to function as a full-featured input and output stream in C.
The mulle-c/mulle-buffer library provides a dynamic memory buffer that doubles as a type-safe, allocator-aware stream implementation. Unlike static arrays or C strings, this library treats the underlying byte array as a linear sequence with a movable cursor, enabling both sequential consumption and production of data. According to the source code in src/mulle-buffer.h, the buffer maintains a _curr pointer that tracks the current read/write position, giving it the same logical capabilities as a FILE* stream but with optional stack allocation and flexible memory management.
How mulle-buffer Implements Stream Semantics
The Stream Cursor Architecture
At the core of mulle-buffer's stream capability is the _curr pointer maintained in struct mulle__buffer (defined in src/mulle-buffer.h). This cursor marks the current read/write position within the underlying storage, while _storage points to the allocation start and _sentinel marks the boundary. The public API forwards seek operations to internal implementations: mulle_buffer_set_seek calls _mulle__buffer_set_seek, and mulle_buffer_get_seek calls _mulle__buffer_get_seek, enabling standard SEEK_SET, SEEK_CUR, and SEEK_END semantics.
Input Stream Operations
For reading, the library provides mulle_buffer_get_char, mulle_buffer_get_byte, and mulle_buffer_get_data. These functions read from the _curr position and automatically advance the cursor, mimicking the behavior of fgetc() and fread(). This architecture allows the buffer to function as an input stream for parsing protocols or tokenizing data without modifying the underlying storage.
Output Stream Operations
Write operations including mulle_buffer_add_byte, mulle_buffer_add_string, and mulle_buffer_advance write sequentially at the _curr position. When using a flexible buffer, the storage automatically expands through the associated allocator when capacity is exceeded, similar to how a dynamic file stream grows to accommodate new data.
Reading from mulle-buffer as an Input Stream
To use mulle-buffer as a read-only input stream, initialize it with static storage and seek to specific positions before reading. The mulle_buffer_init_inflexable_with_static_bytes function treats the source array as fixed-capacity stream storage.
#include <mulle-buffer/mulle-buffer.h>
#include <stdio.h>
void demo_read_stream(void)
{
static const char src[] = "Mulle‑Buffer demo\n";
struct mulle_buffer buf;
/* Initialise a non‑growable buffer that uses the static array as storage */
mulle_buffer_init_inflexable_with_static_bytes(&buf,
(void *)src,
sizeof(src));
/* Move to the 7th byte ('B') */
mulle_buffer_set_seek(&buf, SEEK_SET, 7);
int ch;
while ((ch = mulle_buffer_get_char(&buf)) != '\n' && ch != EOF)
putchar(ch); /* prints “Buffer” */
mulle_buffer_done(&buf);
}
Key implementation details: The set_seek function positions the cursor at byte 7, and get_char reads sequentially until newline. Because the buffer is initialized as inflexible, it cannot grow beyond the static source array, ensuring read-only safety for the input stream.
Writing to mulle-buffer as an Output Stream
For output streaming, the mulle_buffer_do macro creates a stack-allocated flexible buffer that automatically manages memory and cleanup. This pattern provides RAII-style resource management for stream writing.
#include <mulle-buffer/mulle-buffer.h>
#include <stdio.h>
void demo_write_stream(void)
{
/* The macro creates a stack‑based buffer that grows automatically. */
mulle_buffer_do (buf)
{
mulle_buffer_add_string(&buf, "Hello, ");
mulle_buffer_add_string(&buf, "world!");
/* Append a newline */
mulle_buffer_add_char(&buf, '\n');
/* Print the accumulated data */
printf("%s", mulle_buffer_get_string(&buf));
} /* <- automatically calls mulle_buffer_done(&buf) */
}
Key implementation details: The mulle_buffer_do macro declares the buffer, initializes it with the default allocator, and ensures mulle_buffer_done is called at block exit to free resources. The add_string and add_char functions write sequentially at the current cursor position, advancing _curr automatically.
Automatic Flushing with mulle_flushablebuffer
The companion type mulle_flushablebuffer (declared in src/mulle-flushablebuffer.h and implemented in src/mulle-flushablebuffer.c) transforms a standard buffer into a write-only output stream that automatically flushes contents to a user-supplied callback. The flusher function (typically fwrite or a custom writer) is invoked when internal storage fills up via _mulle_flushablebuffer_flush or when the buffer is destroyed via mulle_flushablebuffer_done.
#include <mulle-buffer/mulle-buffer.h>
#include <stdio.h>
void dump_to_file(FILE *fp, const void *bytes, size_t len)
{
struct mulle_flushablebuffer fbuf;
struct mulle_buffer *buf;
/* 1 KB local storage; when full it will be flushed via fwrite */
mulle_flushablebuffer_init(&fbuf,
/*storage*/ (char[1024]){0},
1024,
(mulle_flushablebuffer_flusher_t *)fwrite,
fp);
buf = (struct mulle_buffer *)&fbuf; /* treat as a normal buffer */
mulle_buffer_add_string(buf, "---\n");
mulle_buffer_hexdump(buf, bytes, len, 0, mulle_buffer_hexdump_default);
mulle_buffer_add_string(buf, "---\n");
/* Ensure any remaining data is written */
mulle_flushablebuffer_done(&fbuf);
}
Key implementation details: The mulle_flushablebuffer_init function accepts a static storage array (1KB in this example), a flusher callback, and a context pointer (the FILE*). Data is written using the standard mulle_buffer_* API; when the 1024-byte capacity is exhausted, the buffer automatically flushes to the file via fwrite. The final done call ensures no data remains in the internal buffer.
Summary
- Byte-stream abstraction: mulle-buffer implements a
_currcursor mechanism insrc/mulle-buffer.hthat supports sequential read/write operations identical toFILE*streams. - Bidirectional I/O: The library supports both input streaming (via
mulle_buffer_get_*functions) and output streaming (viamulle_buffer_add_*functions) with full seek support forSEEK_SET,SEEK_CUR, andSEEK_END. - Flexible and inflexible modes: Use
mulle_buffer_dofor auto-growing output streams, ormulle_buffer_init_inflexable_with_static_bytesfor fixed-size input streams. - Automatic flushing: The
mulle_flushablebuffertype insrc/mulle-flushablebuffer.cadds write-behind buffering capabilities, automatically calling a user-provided flusher when storage limits are reached. - Type-safe and allocator-aware: Unlike raw
FILE*pointers, mulle-buffer streams are type-safe and integrate with custom memory allocators while supporting stack allocation via convenience macros.
Frequently Asked Questions
Does mulle-buffer support random access seeking like fseek?
Yes, mulle-buffer implements full POSIX-compatible seek semantics through mulle_buffer_set_seek and mulle_buffer_get_seek. According to the implementation in src/mulle-buffer.h, these functions forward to _mulle__buffer_set_seek which supports SEEK_SET (absolute position), SEEK_CUR (relative offset), and SEEK_END (offset from end) operations, allowing random access within the buffer's storage limits.
What distinguishes mulle_flushablebuffer from standard mulle_buffer?
mulle_flushablebuffer is a specialized wrapper that adds automatic flushing behavior to the base buffer implementation. While a standard mulle_buffer stores all data in memory until manually extracted, a flushable buffer invokes a user-supplied callback (such as fwrite) whenever its internal storage fills up or when mulle_flushablebuffer_done is called. This makes it suitable for streaming large datasets to files or network sockets without loading the entire dataset into RAM.
Can mulle-buffer integrate with existing FILE* based APIs?
Yes, through the flushable buffer pattern demonstrated in src/mulle-flushablebuffer.h. You can initialize a mulle_flushablebuffer with fwrite as the flusher callback and a FILE* pointer as the context. This allows the buffer to function as an intermediate stream that automatically writes to standard C file handles while maintaining the performance benefits of buffered I/O.
Is the stream implementation allocator-aware for memory-constrained environments?
Yes, mulle-buffer streams are fully allocator-aware. The flexible buffer mode used in mulle_buffer_do accepts a custom allocator for automatic growth, while inflexible modes use pre-allocated static storage suitable for embedded systems. This design allows the same stream API to function in both high-performance server environments and memory-constrained microcontroller applications without code changes.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →