zstr.h is a modern, single-header string library for C projects. Designed to mimic the architecture of C++ std::string (specifically Small String Optimization and Views), it offers a safe, convenient API while remaining pure C.
It includes a robust C++17 wrapper for mixed codebases, and optional Lua 5.x bindings for high-performance string manipulation in scripts.
- Small String Optimization (SSO): Strings shorter than 23 bytes are stored directly on the stack (inside the struct), avoiding heap allocation entirely.
- View Semantics: Distinct
zstr(owning) andzstr_view(non-owning) types allow for zero-copy slicing and parsing. - Safe & Auto-Growing: Buffer overflows are handled by automatic reallocation.
- C++ Support: Includes a full C++ class wrapper (
z_str::string) with RAII, move semantics, andstd::string_viewcompatibility. - Lua Support: Provides a mutable string buffer for Lua to avoid garbage generation during complex string building.
- UTF-8 Aware: Includes helpers for UTF-8 validation and rune counting.
- Zero Dependencies: Only standard C headers used.
zstr is a concrete type, so setup is straightforward.
- Copy
zstr.h(andzcommon.hif separated) into your project's include directory. - Include it in your code.
To use zstr in Lua, you must compile the binding into a shared library (zstr.so or zstr.dll).
# Compile the module (adjust paths to your Lua headers).
gcc -O3 -shared -fPIC -o zstr.so bindings/lua/zstr_module.c -Iinclude -I/usr/include/lua5.4 -llua5.4You can also use the Makefile with:
make luaYou will need to change the
Makefileto support different versions.
#include <stdio.h>
#include "zstr.h"
int main(void)
{
// Initialize (SSO avoids malloc here).
zstr s = zstr_lit("Hello");
// Concatenate using printf-style formatting.
zstr_fmt(&s, ", %s!", "World");
// Access C-string safely.
printf("%s\n", zstr_cstr(&s)); // "Hello, World!"
// Create a zero-copy view of the first 5 chars.
zstr_view v = zstr_sub(zstr_as_view(&s), 0, 5);
// Cleanup.
zstr_free(&s);
return 0;
}The library detects C++ compilers automatically. To avoid naming collisions with the C struct zstr, the C++ classes live in the z_str namespace.
#include <iostream>
#include "zstr.h"
int main()
{
// RAII handles memory automatically (Constructor/Destructor).
z_str::string s = "Hello";
// Operator overloads.
s += " World!";
// Modern C++17 string_view support.
z_str::view v = s;
// Static formatting helper (Safety Warning: Only pass POD types!).
z_str::string log = z_str::string::fmt("ID: %d", 42);
std::cout << s << std::endl; // "Hello World!"
return 0;
}zstr acts as a high-performance mutable string buffer for Lua. It avoids the garbage collection overhead of repeated string concatenation.
local zstr = require("zstr")
-- Create a buffer (allocates once).
local buf = zstr.new("Start: ")
-- Append efficiently (no new Lua objects created).
buf:append("User: ")
:append("Alice")
:append(" [Admin]")
-- Modify in-place.
buf:upper() -- "START: USER: ALICE [ADMIN]"
buf:trim()
-- Convert back to Lua string only when needed.
print(tostring(buf))
print("Length: " .. #buf)Initialization & Creation
| Function | Description |
|---|---|
zstr_init() |
Returns an empty string {0}. |
zstr_from(const char *s) |
Creates a new zstr from a standard C-string. |
zstr_from_len(const char *p, size_t len) |
Creates a zstr from a buffer and explicit length. |
zstr_lit("literal") |
Optimized macro for string literals (calculates length at compile time). |
zstr_dup(const zstr *s) |
Creates a deep copy of an existing zstr. |
zstr_with_capacity(size_t cap) |
Creates an empty string with pre-allocated heap capacity. |
zstr_read_file(path) |
Reads an entire file into a new zstr. Returns empty on failure. |
zstr_own(ptr, len, cap) |
Takes ownership of a raw malloc'd buffer. |
Memory Management
| Function | Description |
|---|---|
zstr_free(s) |
Frees the string if it is on the heap and resets it to empty. |
zstr_clear(s) |
Sets length to 0 but preserves capacity. |
zstr_reserve(s, cap) |
Ensures the string has space for at least cap bytes. |
zstr_shrink_to_fit(s) |
Reduces heap usage to fit the length (or moves back to SSO). |
zstr_take(s) |
Returns the raw malloc'd pointer and resets the zstr (caller must free). |
Modification
| Function | Description |
|---|---|
zstr_cat(s, str) |
Appends a C-string to the end. |
zstr_cat_len(s, ptr, len) |
Appends a raw buffer of known length. |
zstr_push(s, char) |
Appends a single character (alias for zstr_push_char). |
zstr_pop_char(s) |
Removes and returns the last character. |
zstr_fmt(s, fmt, ...) |
Appends a formatted string (printf-style). |
zstr_join(arr, n, delim) |
Joins an array of strings into a new zstr. |
zstr_trim(s) |
Removes leading and trailing whitespace in-place. |
zstr_to_lower(s) |
Converts to lowercase in-place (ASCII only). |
zstr_to_upper(s) |
Converts to uppercase in-place (ASCII only). |
zstr_replace(s, old, new) |
Replaces all occurrences of old with new. |
Accessors & Helpers
| Function | Description |
|---|---|
zstr_len(s) |
Returns the current length (excluding null terminator). |
zstr_data(s) |
Returns a pointer to the mutable data buffer. |
zstr_cstr(s) |
Returns a const char* safe for C APIs. |
zstr_is_empty(s) |
Returns true if the string length is 0. |
zstr_is_long(s) |
Returns true if the string is currently heap-allocated. |
Comparison & Search
| Function | Description |
|---|---|
zstr_eq(a, b) |
Returns true if strings are equal (faster than strcmp). |
zstr_eq_ignore_case(a, b) |
Returns true if strings are equal (case-insensitive, ASCII only). |
zstr_cmp(a, b) |
Standard strcmp behavior for zstr objects. |
zstr_find(s, needle) |
Returns index of first occurrence or -1 if not found. |
zstr_contains(s, needle) |
Returns true if the string contains the substring. |
zstr_starts_with(s, pre) |
Checks if string starts with prefix. |
zstr_ends_with(s, suf) |
Checks if string ends with suffix. |
Views & Slices (Zero-Copy)
| Function | Description |
|---|---|
ZSV("lit") |
Macro to create a zstr_view from a literal. |
zstr_as_view(s) |
Returns a zstr_view covering the whole string. |
zstr_view_from(cstr) |
Creates a view from a C-string. |
zstr_from_view(v) |
Converts a view back into an owning zstr (allocates). |
zstr_sub(v, start, len) |
Returns a view slice (substring) without copying memory. |
zstr_view_eq(v, cstr) |
Checks if view equals a C-string. |
zstr_view_eq_view(a, b) |
Checks if two views are equal. |
zstr_view_starts_with(v, pre) |
Checks if view starts with prefix. |
zstr_view_ends_with(v, suf) |
Checks if view ends with suffix. |
zstr_view_lstrip(v) |
Returns view with leading whitespace removed. |
zstr_view_rstrip(v) |
Returns view with trailing whitespace removed. |
zstr_view_trim(v) |
Returns view with both ends trimmed. |
zstr_view_to_int(v, out) |
Parses an integer from a view (like atoi). |
UTF-8 Support
| Function | Description |
|---|---|
zstr_is_valid_utf8(s) |
Validates string is strict UTF-8 (rejects overlongs/surrogates). |
zstr_count_runes(s) |
Counts the number of actual UTF-8 Runes (not bytes). |
zstr_next_rune(ptr) |
Decodes next rune and advances pointer. Returns 0xFFFD on error. |
Iteration (Splitting)
| Function | Description |
|---|---|
zstr_split_init(src, delim) |
Initializes a split iterator (zstr_split_iter). |
zstr_split_next(it, out) |
Advances iterator and populates out (view) with the next part. |
Extensions (Experimental)
If you are using a compiler that supports __attribute__((cleanup)) (like GCC or Clang), you can use the Auto-Cleanup extension.
| Macro | Description |
|---|---|
zstr_autofree |
Declares a variable that automatically calls zstr_free when it leaves scope. |
Example:
void log_status()
{
zstr_autofree msg = zstr_from("Status: ");
zstr_cat(&msg, "OK");
// msg is freed automatically here.
}The C++ wrapper is defined in the z_str namespace to avoid collisions with the C struct. It strictly adheres to RAII principles and is designed to drop into existing C++11/17 projects.
An owning string class that internally manages a zstr struct.
Constructors & Management
| Method | Description |
|---|---|
string() |
Default constructor (empty). |
string(const char*) |
Construct from C-string. |
string(std::string_view) |
Construct from C++17 string view. |
own(ptr, len, cap) |
Static. Wraps an existing malloc'd buffer without copying. |
from_file(path) |
Static. Reads entire file into a string. |
release() |
Returns the raw char* and empties the object. Caller must free(). |
Access & Iterators
| Method | Description |
|---|---|
c_str(), data() |
Returns const char*. |
size(), length() |
Returns length in bytes. |
capacity() |
Returns current allocated capacity. |
is_empty() |
Returns true if length is 0. |
operator[] |
Mutable/Const access to character at index. |
front(), back() |
Access first/last character. |
begin(), end() |
Standard iterators (pointers) compatible with STL algorithms. |
Modification
| Method | Description |
|---|---|
append(s) / += |
Appends C-string, character, or other z_str::string. |
push_back(c) |
Appends a single char. |
pop_back() |
Removes and returns the last char. |
replace(old, new) |
Replaces all occurrences of string old with new. |
to_lower(), to_upper() |
In-place case conversion (ASCII). |
trim() |
In-place whitespace removal. |
clear() |
Sets length to 0 (capacity remains). |
fmt(fmt, ...) |
Static. Creates a string via printf formatting. Warning: Pass only POD types ( int, char*), not C++ objects. |
Search & Utilities
| Method | Description |
|---|---|
find(needle) |
Returns index of substring or -1. |
contains(needle) |
Returns true if substring exists. |
starts_with(s) |
Returns true if string starts with s. |
ends_with(s) |
Returns true if string ends with s. |
split(delim) |
Returns a split_iterable for use in range-based for loops. Safety: Deleted for r-values (temporaries) to prevent dangling views. |
rune_count() |
Returns the number of UTF-8 code points. |
is_valid_utf8() |
Returns true if the string contains valid UTF-8. |
A lightweight, non-owning wrapper around zstr_view. Compatible with std::string_view (C++17).
| Method | Description |
|---|---|
view(string&) |
Implicit conversion from z_str::string. |
sub(start, len) |
Returns a new view slice. |
lstrip(), rstrip(), trim() |
Returns a new view with whitespace removed. |
to_int(out) |
Parses view to integer. Returns true on success. |
starts_with, ends_with |
Predicate checks. |
operator== |
Compares with view, string, or const char*. |
The Lua module exports a zstr table with constructors. Instances are userdata objects with methods.
Constructors
| Function | Description |
|---|---|
zstr.new([str]) |
Creates a new buffer, optionally initialized with str. |
zstr.from_file(path) |
Reads an entire file into a buffer. |
Buffer Methods
| Method | Description |
|---|---|
s:append(str...) |
Appends one or more strings to the buffer. Returns self. |
s:reserve(n) |
Pre-allocates capacity for n bytes. |
s:clear() |
Empties the buffer (length 0). |
s:clone() |
Returns a deep copy of the buffer. |
s:capacity() |
Returns the current allocated capacity. |
Transformations (In-Place)
| Method | Description |
|---|---|
s:trim() |
Removes leading/trailing whitespace. |
s:upper() |
Converts to uppercase (ASCII). |
s:lower() |
Converts to lowercase (ASCII). |
s:replace(old, new) |
Replaces all occurrences of old with new. |
Queries
| Method | Description |
|---|---|
s:contains(sub) |
Returns true if sub is found. |
s:starts_with(sub) |
Returns true if buffer starts with sub. |
s:ends_with(sub) |
Returns true if buffer ends with sub. |
s:is_valid_utf8() |
Returns true if content is valid UTF-8. |
s:rune_count() |
Returns the number of UTF-8 characters. |
Utilities
| Method | Description |
|---|---|
s:split(delim) |
Returns a Lua table (array) of strings split by delim. |
#s (Len operator) |
Returns the length in bytes. |
tostring(s) |
Converts the buffer to a standard Lua string. |
By default, zstr.h uses the standard C library functions (malloc, realloc, free).
However, you can override these to use your own memory subsystem (e.g., Memory Arenas, Pools, or Debug Allocators).
To use a custom allocator globally across all z-libs, define the Z_ macros before including zstr.h.
// my_config.h
#define Z_MALLOC(sz) my_malloc(sz)
#define Z_CALLOC(n, sz) my_calloc(n, sz)
#define Z_REALLOC(p, sz) my_realloc(p, sz)
#define Z_FREE(p) my_free(p)
#include "zstr.h"If you need a specific allocator just for strings, use the library-specific macros.
// Example: Strings use a specific heap, other z-libs use default.
#define Z_STR_MALLOC(sz) custom_alloc(sz)
#define Z_STR_REALLOC(p, sz) custom_realloc(p, sz)
#define Z_STR_FREE(p) custom_free(p)
#include "zstr.h"Note for C++: If you override these macros manually, ensure your
MALLOCandREALLOCmacros cast their result to(char*)to satisfy C++ strict typing, thoughzstr.hhandles this internally for standard headers (just a gentle reminder).
zstr structs are 32 bytes (on 64-bit systems).
- Long Mode: 8 bytes pointer, 8 bytes length, 8 bytes capacity, 1 byte flag, 7 bytes padding.
- Short Mode: 23 bytes buffer, 1 byte length.
This means strings of length 0-22 are stored entirely within the struct definition. Creating them or modifying them requires zero heap interaction.