How to Create a Persistent String Using mulle-buffer
To create a persistent string using mulle-buffer, first ensure the buffer is null-terminated with mulle_buffer_make_string(), then call mulle_buffer_extract_string() to obtain a heap-allocated C string that remains valid after the buffer itself is destroyed.
The mulle-c/mulle-buffer repository provides a dynamic byte array implementation for C that can be safely converted into independent, null-terminated strings. Unlike temporary buffer contents that become invalid when the buffer scope ends, properly extracted strings persist on the heap until explicitly freed. This guide demonstrates how to create a persistent string using mulle-buffer through the exact API calls and macro-based workflows defined in the source code.
The Two-Step Process for Persistent Strings
Creating a persistent string requires two distinct operations: ensuring null termination and detaching the data from the buffer structure. According to the implementation in src/mulle--buffer.c, these steps guarantee that the returned pointer remains valid independent of the original buffer's lifetime.
Step 1: Null-Terminate with mulle_buffer_make_string
Before extraction, the buffer must contain a terminating '\0' byte. The function mulle_buffer_make_string() (backed by _mulle__buffer_make_string in src/mulle--buffer.c at lines 196-215) appends a null terminator if one is not already present. This function returns status codes indicating whether the buffer required modification, ensuring the internal storage is properly prepared for C string operations.
Step 2: Extract with mulle_buffer_extract_string
The mulle_buffer_extract_string() function, declared as an inline wrapper in src/mulle-buffer.h at lines 702-710, performs the actual extraction:
static inline void *mulle_buffer_extract_string( struct mulle_buffer *buffer )
{
return( _mulle__buffer_extract_string( (struct mulle__buffer *) buffer,
buffer->_allocator));
}
Internally, the implementation in src/mulle--buffer.c (lines 218-227) first invokes the make_string logic, then shrinks the allocation to the exact size via _mulle__buffer_size_to_fit, and finally returns the pointer. The returned string is allocated using the buffer's configured allocator and must be freed with mulle_free() when no longer needed.
Manual API Implementation
For explicit control over buffer lifecycle and string extraction, use the manual API. This approach is defined in src/mulle-buffer.h and allows you to manage each allocation step discretely.
#include "mulle-buffer.h"
int main(void)
{
struct mulle_buffer *buf;
char *persistent;
/* 1️⃣ Create a flexible buffer that will grow as needed */
buf = mulle_buffer_create( NULL ); /* uses default allocator */
/* 2️⃣ Build the string */
mulle_buffer_add_string( buf, "Hello, " );
mulle_buffer_add_string( buf, "world!" );
/* 3️⃣ Ensure null‑termination (optional – extract does it anyway) */
mulle_buffer_make_string( buf );
/* 4️⃣ Extract a persistent copy */
persistent = mulle_buffer_extract_string( buf );
/* 5️⃣ The original buffer can now be destroyed safely */
mulle_buffer_destroy( buf );
/* 6️⃣ Use the string */
printf("%s\n", persistent);
/* 7️⃣ Free the persistent copy when done */
mulle_free( persistent );
return 0;
}
Key API references from the source:
mulle_buffer_create→ defined insrc/mulle-buffer.hat line 258mulle_buffer_add_string→ defined insrc/mulle-buffer.hat line 1354mulle_buffer_make_string→ defined insrc/mulle-buffer.hat lines 1644-1655mulle_buffer_extract_string→ defined insrc/mulle-buffer.hat lines 702-710
Automated Extraction with mulle_buffer_do_string
For most use cases, the mulle_buffer_do_string macro automates buffer creation, string extraction, and cleanup. Defined in src/mulle-buffer.h at lines 640-679, this macro uses a complex for-loop construct to manage scope automatically.
The macro expansion creates a temporary mulle_buffer on the stack (or via the provided allocator), executes your code block, automatically calls mulle_buffer_extract_string, and cleans the temporary buffer when exiting the scope.
#include "mulle-buffer.h"
int main(void)
{
char *s;
/* The macro handles creation, extraction and cleanup */
mulle_buffer_do_string( buf, NULL, s )
{
mulle_buffer_add_string( buf, "Persistent " );
mulle_buffer_add_string( buf, "string created with a macro" );
break; /* break out of the block – macro will still extract */
}
/* `s` now points to a heap‑allocated, null‑terminated string */
printf("%s\n", s);
mulle_free( s ); /* remember to free it */
return 0;
}
The macro definition itself (lines 640-679) implements a state machine using pointer arithmetic to ensure extraction happens exactly once after the block executes, even if you exit early with a break statement.
Custom Allocator Support
When working with non-standard memory management, use mulle_buffer_do_allocator (defined in src/mulle-buffer.h at lines 731-764) to inject a custom allocator for both the temporary buffer and the final extracted string.
#include "mulle-buffer.h"
#include "mulle-allocator.h"
int main(void)
{
struct mulle_allocator *alloc = mulle_allocator_create();
char *s;
mulle_buffer_do_allocator( buf, alloc, s )
{
mulle_buffer_add_string( buf, "Allocated with custom allocator" );
}
printf("%s\n", s);
mulle_free( s );
mulle_allocator_destroy( alloc );
return 0;
}
This pattern ensures that mulle_buffer_extract_string uses your specified allocator when shrinking the allocation to fit and returning the persistent pointer.
Summary
- Persistent strings require explicit extraction from a
mulle_bufferusingmulle_buffer_extract_string(), which internally calls_mulle__buffer_extract_string(lines 218-227 insrc/mulle--buffer.c). - Null-termination is guaranteed by
mulle_buffer_make_string()(backed by lines 196-215 insrc/mulle--buffer.c), though extraction functions typically invoke this automatically. - Memory ownership transfers to the caller upon extraction; you must release the string with
mulle_free()to avoid leaks. - The
mulle_buffer_do_stringmacro (lines 640-679 insrc/mulle-buffer.h) provides a safe, scope-bound alternative that automates cleanup. - Custom allocators are supported via
mulle_buffer_do_allocatorfor specialized memory management requirements.
Frequently Asked Questions
What is the difference between mulle_buffer_make_string and mulle_buffer_extract_string?
mulle_buffer_make_string() ensures the buffer contains a null terminator but keeps the data attached to the buffer structure, whereas mulle_buffer_extract_string() first ensures null termination, then shrinks the allocation to fit exactly, detaches it from the buffer, and returns an independent pointer. Only the extracted pointer represents a persistent string that survives buffer destruction.
Do I need to call mulle_buffer_make_string before mulle_buffer_extract_string?
No, calling mulle_buffer_make_string() explicitly is optional. According to the implementation in src/mulle--buffer.c at lines 218-227, mulle_buffer_extract_string() internally invokes the make_string logic to guarantee null termination before performing the extraction and allocation shrink.
How do I free a persistent string created with mulle-buffer?
Strings returned by mulle_buffer_extract_string() are allocated using the buffer's allocator (typically the default allocator). You must release this memory using mulle_free() when the string is no longer needed to prevent memory leaks.
Can I create persistent strings with custom memory allocators?
Yes. Use the mulle_buffer_do_allocator macro (lines 731-764 in src/mulle-buffer.h) to specify a custom struct mulle_allocator. The extraction process will use your allocator for both the temporary buffer operations and the final persistent string allocation, ensuring consistent memory management throughout the lifecycle.
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 →