A modern, efficient, single-file string library for C featuring:
- Direct C compatibility -
ds_string
works with ALL C functions (no conversion needed!)
- Reference counting with automatic memory management
- Immutable strings for safety and sharing
- Efficient StringBuilder for string construction
- Unicode support with UTF-8 storage and codepoint iteration
- Zero dependencies - just drop in the
.h
file
- Memory safe - eliminates common C string bugs
Quick Example
#define DS_IMPLEMENTATION
int main() {
printf("%s\n", full);
int result = strcmp(full, "Hello");
FILE* f = fopen(full, "r");
uint32_t codepoint;
printf("U+%04X ", codepoint);
}
return 0;
}
Modern, efficient, single-file string library for C.
DS_DEF ds_codepoint_iter ds_codepoints(ds_string str)
Create an iterator for Unicode codepoints in a string.
char * ds_string
String handle - points directly to null-terminated string data.
Definition dynamic_string.h:125
DS_DEF ds_string ds_append(ds_string str, const char *text)
Append text to a string.
DS_DEF uint32_t ds_iter_next(ds_codepoint_iter *iter)
Get the next Unicode codepoint from iterator.
DS_DEF ds_string ds_new(const char *text)
Create a new string from a C string.
DS_DEF void ds_release(ds_string *str)
Decrement reference count and free memory if last reference.
Unicode codepoint iterator for UTF-8 strings.
Definition dynamic_string.h:516
Major Features
π Direct C Compatibility
The biggest advantage: ds_string
IS a char*
that works with every C function:
FILE* f = fopen(path, "r");
if (strstr(path, "tmp")) { ... }
printf("Path: %s\n", path);
π Reference Counting
- Automatic memory management - no manual
free()
needed
- Efficient sharing - multiple variables can point to same string data
- Copy-on-write - strings are only copied when actually modified
DS_DEF size_t ds_refcount(ds_string str)
Get the current reference count of a string.
DS_DEF ds_string ds_retain(ds_string str)
Increment reference count and return shared handle.
π‘οΈ Immutable Strings
- Thread-safe reading - immutable strings can be safely shared between threads
- Functional style - operations return new strings, originals unchanged
- No accidental modification - prevents common C string bugs
β‘ Efficient StringBuilder
- In-place construction - builds strings efficiently without intermediate allocations
- Single-use design - simple and memory-safe
- Automatic growth - handles capacity management
for (int i = 0; i < 1000; i++) {
}
DS_DEF ds_stringbuilder ds_builder_create(void)
DS_DEF int ds_builder_append(ds_stringbuilder *sb, const char *text)
DS_DEF void ds_builder_destroy(ds_stringbuilder *sb)
DS_DEF ds_string ds_builder_to_string(ds_stringbuilder *sb)
Definition dynamic_string.h:567
π Unicode Support
- UTF-8 storage - compact and C-compatible
- Codepoint iteration - proper Unicode handling
- Mixed text support - seamlessly handle ASCII, emoji, and international text
printf("Bytes: %zu, Codepoints: %zu\n",
uint32_t cp;
printf("U+%04X ", cp);
}
DS_DEF size_t ds_length(ds_string str)
Get the length of a string in bytes.
DS_DEF size_t ds_codepoint_length(ds_string str)
Count the number of Unicode codepoints in a string.
β οΈ Error Handling
Important: NULL Parameter Validation
As of v0.2.2, all functions perform strict NULL parameter validation using assertions:
Benefits:
- Immediate error detection - bugs are caught at the source
- Clear error messages - assertions specify exactly which parameter is NULL
- Consistent behavior - all functions handle NULL parameters the same way
- Development safety - prevents silent failures and undefined behavior
Migration from older versions:
- Code that passes valid (non-NULL) parameters continues to work unchanged
- Code that relied on graceful NULL handling will now trigger assertions
- Use a debugger or assertion handler to catch and fix NULL parameter issues
Usage Patterns
Basic Usage
#define DS_IMPLEMENTATION
Correct Memory Management Patterns
Simple operations:
Transform and replace:
Multiple operations - use StringBuilder:
β Don't do this (memory leaks):
When to Use StringBuilder vs Simple Operations
Use simple operations for:
- One or two concatenations
- Transforming existing strings
- Functional-style processing
Use StringBuilder for:
- Building strings in loops
- Multiple concatenations (3+)
- Performance-critical string construction
- When you don't know final size
API Reference
Core Functions
DS_DEF ds_string ds_substring(ds_string str, size_t start, size_t len)
Extract a substring from a string.
DS_DEF int ds_ends_with(ds_string str, const char *suffix)
Check if string ends with a suffix.
DS_DEF ds_string ds_insert(ds_string str, size_t index, const char *text)
Insert text at a specific position in a string.
DS_DEF ds_string ds_append_char(ds_string str, uint32_t codepoint)
Append a Unicode codepoint to a string.
DS_DEF int ds_starts_with(ds_string str, const char *prefix)
Check if string starts with a prefix.
DS_DEF int ds_is_empty(ds_string str)
Check if a string is empty.
DS_DEF int ds_compare(ds_string a, ds_string b)
Compare two strings lexicographically.
DS_DEF int ds_is_shared(ds_string str)
Check if a string has multiple references.
DS_DEF ds_string ds_concat(ds_string a, ds_string b)
Concatenate two strings.
DS_DEF int ds_find(ds_string str, const char *needle)
Find the first occurrence of a substring.
DS_DEF ds_string ds_prepend(ds_string str, const char *text)
Prepend text to the beginning of a string.
DS_DEF ds_string ds_join(ds_string *strings, size_t count, const char *separator)
Join multiple strings with a separator.
DS_DEF ds_string ds_create_length(const char *text, size_t length)
Create a string from a buffer with explicit length.
StringBuilder Functions
DS_DEF const char * ds_builder_cstr(const ds_stringbuilder *sb)
DS_DEF size_t ds_builder_capacity(const ds_stringbuilder *sb)
DS_DEF void ds_builder_clear(ds_stringbuilder *sb)
DS_DEF ds_stringbuilder ds_builder_create_with_capacity(size_t capacity)
DS_DEF int ds_builder_insert(ds_stringbuilder *sb, size_t index, const char *text)
DS_DEF int ds_builder_append_char(ds_stringbuilder *sb, uint32_t codepoint)
DS_DEF int ds_builder_append_string(ds_stringbuilder *sb, ds_string str)
DS_DEF size_t ds_builder_length(const ds_stringbuilder *sb)
Unicode Functions
DS_DEF int ds_iter_has_next(const ds_codepoint_iter *iter)
Check if iterator has more codepoints.
DS_DEF uint32_t ds_codepoint_at(ds_string str, size_t index)
Get Unicode codepoint at specific index.
Convenience Macros
#define ds_empty() ds_new("")
#define ds_from_literal(lit) ds_new(lit)
Configuration
Override behavior with macros before including:
#define DS_MALLOC my_malloc
#define DS_FREE my_free
#define DS_REALLOC my_realloc
#define DS_STATIC
#define DS_IMPLEMENTATION
Building Examples
git clone https://github.com/yourusername/dynamic_string.h
cd dynamic_string.h
mkdir build && cd build
cmake ..
make
./example
Version History
v0.2.2 (Current)
- BREAKING CHANGE: All functions now assert on NULL
ds_string
parameters instead of handling them gracefully
- API Safety: Consistent assertion-based parameter validation across entire API
- Error Detection: NULL parameters now trigger immediate assertion failures with descriptive error messages
- Testing: Implemented comprehensive assertion testing framework with signal handling
- Documentation: Updated all function documentation to specify parameters "must not be NULL"
- Compatibility: Maintains 100% backward compatibility for valid (non-NULL) usage patterns
- Testing: All 71 tests pass with new assertion-based error handling
v0.2.1
- Critical Fix: Buffer overflow vulnerability in
ds_create_length()
- now correctly handles requested length vs source string length
- Critical Fix: Segmentation fault in test suite caused by dangerous NULL parameter testing
- Security: Added comprehensive NULL parameter validation with assertions for
ds_create_length()
, ds_prepend()
, ds_insert()
, ds_substring()
, ds_replace()
, and ds_replace_all()
- Fixed:
ds_insert()
beyond-bounds behavior now inserts at string end instead of returning unchanged
- Testing: Corrected 6 failing tests that had incorrect expectations, all 73 tests now pass
- API Safety: Functions now fail fast with clear assertion messages instead of silent undefined behavior
v0.2.0
- Added: Atomic reference counting support (
DS_ATOMIC_REFCOUNT
) for safe concurrent reference sharing
- Added:
ds_contains()
- clean boolean check for substring presence
- Added:
ds_find_last()
- find last occurrence of substring
- Added:
ds_hash()
- FNV-1a hash function for use in hash tables
- Added:
ds_compare_ignore_case()
- case-insensitive string comparison
- Added:
ds_truncate()
- smart truncation with optional ellipsis
- Added:
ds_format_v()
- va_list version of ds_format for wrapper functions
- Added:
ds_escape_json()
/ ds_unescape_json()
- JSON string escaping/unescaping
- Removed: Version macros (not needed for single-header libraries)
- Improved: All complex string building operations now use StringBuilder internally for efficiency
- Documentation: Added CLAUDE.md for AI assistance and comprehensive usage examples
v0.1.0
- Added: Version macros (
DS_VERSION_MAJOR
, DS_VERSION_MINOR
, DS_VERSION_PATCH
, DS_VERSION_STRING
)
- Added: Programmatic version checking with
DS_VERSION
macro
- Documentation: Comprehensive doxygen comments with code examples and cross-references
- Build: GitHub Actions workflow for automatic documentation generation and deployment
v0.0.2
- Breaking:
ds()
renamed to ds_new()
for clarity
- Breaking:
ds_sb_*
functions renamed to ds_builder_*
for readability
- Breaking: Removed
ds_cstr()
- direct C compatibility
- Breaking:
ds_ref_count()
renamed to ds_refcount()
- Breaking: StringBuilder becomes single-use after
ds_builder_to_string()
- Fixed: Memory corruption bugs in StringBuilder
- Improved: Consistent internal API usage
- Added: String transformation functions (
ds_trim
, ds_split
, ds_format
)
v0.0.1
- Initial release with basic functionality
Memory Layout
Each string uses a single allocation with metadata and data stored together:
Memory: [refcount|length|string_data|\0]
^
ds_string points here
This provides:
- Better cache locality - metadata and data in same allocation
- Fewer allocations - no separate buffer allocation
- Direct C compatibility - pointer points to actual string data
- Automatic null termination - works with C string functions
License
Dual licensed under your choice of:
- [MIT License](LICENSE-MIT)
- [The Unlicense](LICENSE-UNLICENSE) (public domain)
Choose whichever works better for your project!
Design Philosophy
This library brings modern string handling to C by combining:
- Rust's approach - UTF-8 storage with codepoint iteration
- Swift's model - reference counting with automatic memory management
- STB-style delivery - single header file, easy integration
- C compatibility - works seamlessly with existing C code
The result is a string library that's both safe and fast, making C string handling as pleasant as higher-level languages while maintaining C's performance characteristics and perfect compatibility with existing C APIs.