Small standalone C library that provides extensible C-style strings and extensible arrays of those strings, for Unix and Windows.
cstring provides one resizeable string, cstring_t, and a vector of those strings, cstring_vector_t. A cstring_t is always a length, a pointer, a capacity, and flags. The flags select a memory contract. An owned growable string is the default. Fixed, borrowed, auto-buffer, and readonly are the other contracts. Which heap owns the memory is a separate choice: realloc by default, and the Windows heaps where those flags exist.
The C API has no non-standard dependencies. Building tests requires STLSoft and xTests (and may optionally recognise shwild).
Ordinary code uses the default contract and never sets a flag. The other contracts exist so a caller can cap growth, write into a buffer they already have, or select a Windows heap, without a second string type.
#include <cstring/cstring.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
cstring_t cs;
CSTRING_RC rc = cstring_create(&cs, "Hello");
if (CSTRING_RC_SUCCESS != rc)
{
return EXIT_FAILURE;
}
printf("%s\n", cs.ptr);
cstring_destroy(&cs);
return EXIT_SUCCESS;
}Pass memory flags to cstring_createEx() or cstring_createLenEx(). For a borrowed buffer, arena is that buffer and capacity is its size. With no memory flags, those parameters are ignored and the string is an owned heap allocation.
| Mode | Flags | Familiar form | If it cannot grow |
|---|---|---|---|
| Owned, growable | (none) | A heap std::string, or Rust String |
CSTRING_RC_OUTOFMEMORY |
| Owned, fixed | CSTRING_F_MEMORY_IS_FIXED |
That same owned string, with a hard ceiling | CSTRING_RC_EXCEEDFIXEDCAPACITY |
| Borrowed | CSTRING_F_MEMORY_IS_BORROWED (implies fixed) |
A caller-owned char buf[N], writable up to N |
CSTRING_RC_EXCEEDBORROWEDCAPACITY |
| Auto-buffer | CSTRING_F_MEMORY_IS_BORROWED | CSTRING_F_MEMORY_CAN_GROW_TO_HEAP |
stlsoft::auto_buffer / llvm::SmallString |
Spills to the heap, then stays there |
| Readonly | CSTRING_F_MEMORY_IS_READONLY |
A frozen instance; with borrowed, std::string_view or Rust &str |
CSTRING_RC_READONLY |
std::string SSO keeps a small buffer inside the object; cstring's auto-buffer uses a buffer you supply, of a size you choose, and cstring_t stays four fields; after a spill the instance stays on the heap (Rust's standard String has no SSO).
- Owned, fixed, and borrowed (including Windows allocators, where the host has them): example.c.cstring;
- Auto-buffer: example.c.cstring.auto_buffer;
- Win32 global memory: example.cpp.cstring.global_memory.
The arena flags apply to memory the library owns: the default heap, a fixed owned buffer, and the heap side of an auto-buffer.
| Arena | Flag | Where |
|---|---|---|
realloc |
CSTRING_F_USE_REALLOC (the default) |
Unix and Windows |
| Win32 global memory | CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY |
Windows |
| Process heap | CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY |
Windows |
| COM task allocator | CSTRING_F_USE_WINDOWS_COM_TASK_MEMORY |
Windows |
A few further rules:
cstring_init()storescstring_t_DEFAULT. That instance does not needcstring_destroy(). Everycstring_create*does;cstring_yield2()hands back an owned payload. Borrowed and readonly instances refuse it. A Windows DLL built onreallocreturnsCSTRING_RC_CANNOTYIELDFROMSO;- The character type is
charunlessCSTRING_USE_WIDE_STRINGSis set (normally bothUNICODEand_UNICODEon Windows).CSTRING_NO_USE_WIDE_STRINGSforceschar. That choice is made at compile time. prepare_cmake.sh--wide-stringssets the CMake optionCSTRING_USE_WIDE_STRINGS, which defines the macro on the library and everything that links it. Examples and tests include cstring.helpers.h forCSTRING_T_(),CSTRING_STRCMP_(), andCSTRING_STRNCMP_(). That header is not part of the library contract. Print a payload withcstring_write()/cstring_writeline(); - Custom arenas (
CSTRING_F_USE_CUSTOMARENAFUNCTIONS) are declared and returnCSTRING_RC_CUSTOMARENANOTSUPPORTED.CSTRING_F_MEMORY_IS_OFFSETis set by the implementation and is not a client mode.
Detailed instructions - via CMake, via bundling - are provided in the accompanying INSTALL.md file.
The C API is based around two structures:
cstring_t, which represents a resizeable string instance; andstruct cstring_t { size_t len; /*!< Number of characters. */ cstring_char_t* ptr; /*!< Pointer to the string. If capacity is 0, the value of this member is undetermined. */ size_t capacity; /*!< Number of bytes available. */ cstring_flags_t flags; /*!< Flags. This field belongs to the implementation, and must not be modified by any application code. */ };
cstring_vector_t, which represents a sequence ofcstring_tinstances;struct cstring_vector_t { size_t len; /*!< Number of strings. */ cstring_t* ptr; /*!< Pointer to the first string. If capacity is 0, the value of this member is undetermined. */ size_t capacity; /*!< Number of instances available. */ cstring_flags_t flags; /*!< Flags. This field belongs to the implementation, and must not be modified by any application code. */ };
Supporting scalar typedefs:
cstring_char_t— character type (char, orwchar_twhenCSTRING_USE_WIDE_STRINGSis defined);cstring_flags_t— bit flags controlling allocation and capacity semantics;cstring_hash_t— 64-bit unsigned integer type (uint64_t) representing hash values;
CSTRING_VER— the composite library version;cstring_t_DEFAULT—{ 0, NULL, 0, 0 }, an uninitialisedcstring_t.cstring_init()assigns this;cstring_vector_t_DEFAULT— the same shape for acstring_vector_t;cstring_vector_DEFAULT_CAPACITY— sentinel (~(size_t)0) passed to creators so the implementation chooses the capacity;CSTRING_FROM_END(x)— reverse index forcstring_insert(),cstring_insertLen(),cstring_replace(), andcstring_replaceLen();CSTRING_HASH_DJB2_SEED— djb2 initial seed (5381); also the hash of an empty input;CSTRING_HASH_FNV1A_OFFSET— FNV-1a 64-bit offset basis (0xcbf29ce484222325ULL); also the hash of an empty input;CSTRING_HASH_FNV1A_PRIME— FNV-1a 64-bit prime (0x100000001b3ULL);CSTRING_HASH_SDBM_MULTIPLIER— sdbm multiplier (65599);CSTRING_HASH_SDBM_SEED— sdbm initial seed (0); also the hash of an empty input;
Defined in cstring/cstring.h:
cstring_getStatusCodeString()— returns a NUL-terminated description of aCSTRING_RCcode;cstring_getStatusCodeStringLength()— returns the length of that description, or 0 if the code is not recognised;cstring_setCapacity()— adjusts capacity (subject to fixed / borrowed / readonly rules);cstring_yield2()— yields ownership of the payload (and raw buffer) to the caller;
cstring_init()— initialises an instance to default values (and does not require a following call tocstring_destroy());cstring_create()— creates an instance from a C-style string;cstring_createLen()— creates an instance from a (portion of a) C-style string;cstring_createN()— creates an instance from a number of repetitions of a character value;cstring_createEx()— creates an instance with special characteristics (borrowed buffer, allocator flags, …);cstring_createLenEx()— ascstring_createEx(), from a fixed number of characters;cstring_destroy()— releases resources and resets the instance;
cstring_assign()— assigns a C-style string (may reallocate);cstring_assignLen()— assigns a fixed character count (embedded NULs allowed);cstring_copy()— copies onecstring_tinto another;cstring_append()— appends a C-style string;cstring_appendLen()— appends a fixed character count;cstring_insert()— inserts a C-style string at an index (CSTRING_FROM_ENDsupported);cstring_insertLen()— inserts a fixed character count at an index;cstring_replace()— replaces a section at an index with a C-style string;cstring_replaceLen()— replaces a section at an index with a fixed character count;cstring_replaceAll()— replaces all occurrences of one substring with another;cstring_truncate()— shortens the logical length (capacity unchanged);cstring_swap()— swaps the contents of two instances;
cstring_equal()— non-zero when two strings hold the same code units forlen. ANULLpointer and a zero length are empty.capacityandflagsare ignored;cstring_compare()— negative, zero, or positive order of those same code units. Test equality withcstring_equal();- C++
operator==andoperator!=callcstring_equal();operator<callscstring_compare();
cstring_readline()— reads a line of text from the given text stream into the instance;cstring_write()— writes the string to the given text stream;cstring_writeline()— writes the string followed by a newline to the given text stream;
Declared in cstring/hash.h, which cstring.h includes. The three algorithms return cstring_hash_t (uint64_t). Multibyte and wide entry points exist in every build, whatever ambient cstring_char_t is. A multibyte code unit contributes its one octet. A wide code unit contributes every octet of the wchar_t, low byte first, so "a" and L"a" differ. The wide value depends on sizeof(wchar_t): two octets on Windows, four on Unix. Big-endian and little-endian hosts of the same width agree. A NULL pointer, or a zero length, yields that algorithm's empty-input value and does not read the pointer. The numeric tables below are the multibyte results.
| Suffix | Input | What is hashed |
|---|---|---|
| (none) | cstring_t const* |
ptr for len code units, including embedded NULs |
_mbs |
char const* |
stops at the first NUL |
_wcs |
wchar_t const* |
stops at the first NUL |
_buf |
cstring_char_t const*, size_t |
ambient buffer; calls _mbuf or _wbuf |
_mbuf |
char const*, size_t |
exactly cch code units, including embedded NULs |
_wbuf |
wchar_t const*, size_t |
exactly cch code units, including embedded NULs |
_case is the last suffix of each name (cstring_hash_djb2_mbs_case(), cstring_hash_fnv1a_wbuf_case(), cstring_hash_sdbm_mbuf_case(), and so on). Case folding maps ASCII A-Z to a-z and leaves every other code unit unchanged, independent of the process locale, and then hashes the octets of the folded code unit. It is not a Unicode case-fold.
Names follow cstring_hash_<algorithm><suffix>, for example cstring_hash_djb2(), cstring_hash_fnv1a_wbuf_case(), and cstring_hash_sdbm_mbuf().
From C++11, std::hash<cstring_t> is the FNV-1a hash of ptr and len, converted to size_t. djb2 and SDBM are not used for that specialisation.
The hash starts at CSTRING_HASH_DJB2_SEED (5381). For each octet b the step is hash = ((hash << 5) + hash) + b, which is hash * 33 + b. Daniel J. Bernstein's djb2, as published by Ozan Yigit.
| Input | Hash |
|---|---|
empty, or NULL |
5381 |
"a" |
177670 |
"foobar" |
6953516687550 |
{ 'a', 0, 'b' } length 3 |
193482728 |
octet 0xFF |
177828 |
The length-3 buffer differs from "ab", because the embedded NUL is hashed. 64-bit djb2 matches 32-bit djb2 only while the running total stays below 2^32. "foobar" is already past that point.
The hash starts at CSTRING_HASH_FNV1A_OFFSET (0xcbf29ce484222325). For each octet b the step is hash ^= b and then hash *= CSTRING_HASH_FNV1A_PRIME (0x100000001b3). Fowler, Noll, and Vo FNV-1a, 64-bit.
| Input | Hash |
|---|---|
empty, or NULL |
0xcbf29ce484222325 |
"a" |
0xaf63dc4c8601ec8c |
"foobar" |
0x85944171f73967e8 |
{ 'a', 0, 'b' } length 3 |
0xe5d29919042666b2 |
octet 0xFF |
0xaf64724c8602eb6e |
The hash starts at CSTRING_HASH_SDBM_SEED (0). For each octet b the step is hash * CSTRING_HASH_SDBM_MULTIPLIER + b. The multiplier is 65599. This is the sdbm recurrence published by Ozan Yigit, evaluated in a 64-bit accumulator.
| Input | Hash |
|---|---|
empty, or NULL |
0 |
"a" |
97 |
"foobar" |
0x430d469aa6437b0d |
{ 'a', 0, 'b' } length 3 |
0x612fc3e043 |
octet 0xFF |
255 |
The length-3 buffer differs from "ab", because the embedded NUL is hashed.
Defined in cstring/cstring.vector.h:
cstring_vector_init()— initialises a vector, optionally with a minimum capacity;cstring_vector_create()— creates a vector of a given initial size (elements default-initialised);cstring_vector_destroy()— destroys each element and frees the vector buffer;cstring_vector_truncate()— shortens the vector, destroying trailing elements;cstring_vector_insertAt()— inserts one or morecstring_tinstances at a position;cstring_vector_append()/cstring_vector_prepend()— macros overcstring_vector_insertAt();cstring_vector_readLines()— reads lines from a stream into the vector;
When included in C++ compilation units, cstring/cstring.h provides inline access shims:
- String access shims —
c_str_data(),c_str_len(), andc_str_ptr(), allowingcstring_tinstances to be used directly with STLSoft and generic C++ templates; - Hash access shims —
cstring::hash_djb2(),cstring::hash_djb2_case(),cstring::hash_fnv1a(),cstring::hash_fnv1a_case(),cstring::hash_sdbm(), andcstring::hash_sdbm_case(), overloaded forstruct cstring_t const&,struct cstring_t const*,char const*,wchar_t const*, and the buffer forms(char const* s, size_t cch)and(wchar_t const* s, size_t cch). ANULLargument must be cast, becausechar const*andwchar_t const*are both viable;
Examples live under examples/ (c/ and cpp/). The directory is the subject; the built program is example.<lang>.<subject>. Each has a short README.md. Build them with BUILD_EXAMPLES (on by default); run via run_all_examples.sh.
| Example | Language | Notes |
|---|---|---|
| example.c.cstring | C | Core cstring_t create / assign / append / truncate / copy / swap |
| example.c.cstring.auto_buffer | C | Borrowed buffer that may grow to the heap |
| example.c.cstring_create | C | Minimal cstring_create() |
| example.c.cstring_vector | C | Read lines into cstring_vector_t and sort (input path or --; SIS_EXAMPLE_SMOKE enables no-arg demo) |
| example.cpp.cstring.dynload | C++ | Windows-only dynamic load of the cstring DLL |
| example.cpp.cstring.global_memory | C++ | Windows-only CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY |
Defect reports, feature requests, and pull requests are welcome on https://github.com/synesissoftware/cstring.
See also HOW_YOU_CAN_HELP.md.
The C API has no non-standard dependencies.
| Dependency | Role | Required? |
|---|---|---|
| STLSoft 1.11 | Test headers / remaining C++ tests and examples | ⚪ Tests only (BUILD_TESTING) |
| xTests (≥ 0.26) | Unit / component / scratch tests | ⚪ Tests only (BUILD_TESTING) |
| p99 | Percentiles in performance tests; file_lines suite | ⚪ Optional; tests only (unless NO_P99 / --no-p99) |
| shwild | Enhanced pattern-match assertions in xTests | ⚪ Optional; tests only (unless NO_SHWILD / --no-shwild) |
When supplying '--no-cpp' to prepare_cmake.sh — sets the CMake option NO_CSTRING_CPP_API=ON — C++ examples and remaining C++ tests are omitted; the C unit-tests still require STLSoft and xTests.
When supplying '--no-p99' — sets NO_P99=ON — p99 is not recognised; performance tests still build but omit percentiles and the filesystem file_lines suite.
When supplying '--no-shwild' — sets NO_SHWILD=ON — shwild is not recognised and pattern-match assertions are compiled out; other unit-tests still run.
When supplying '--wide-strings' — sets CSTRING_USE_WIDE_STRINGS=ON — cstring_char_t is wchar_t. Examples and unit/component tests follow that type. Performance tests are not built: they compare char payloads with std::string. CI runs that configuration as windows-cl-wide and windows-mingw-wide.
Projects that call the cstring API, or that require the CMake package cstring:
| Project | Use |
|---|---|
| errni | Builds each result line and writes it with cstring_writeline() |
| lnunique | Requires cstring 4.0 and links cstring::core for line strings |
| rstrip | Accumulates each output line and writes it with cstring_write() |
| shwild.fnmatch | Holds patterns and subject strings as cstring_t |
| STLSoft | Optional; the C unit test output_debug_line.C uses cstring_t when the package is found |
cstring is released under the 3-clause BSD license. See LICENSE for details.