{"id":19134655,"url":"https://github.com/marktsuchida/ssstr","last_synced_at":"2026-06-23T05:31:58.086Z","repository":{"id":41547954,"uuid":"502455124","full_name":"marktsuchida/ssstr","owner":"marktsuchida","description":"Header-only, small-string-optimized string library for C","archived":false,"fork":false,"pushed_at":"2026-02-15T21:14:06.000Z","size":330,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-15T23:40:58.451Z","etag":null,"topics":["c","string","strings"],"latest_commit_sha":null,"homepage":"https://marktsuchida.github.io/ssstr/","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/marktsuchida.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-06-11T20:59:43.000Z","updated_at":"2026-02-15T21:14:09.000Z","dependencies_parsed_at":"2022-07-18T03:16:50.566Z","dependency_job_id":"27a13d9b-34a7-4cec-8967-936f5c482b56","html_url":"https://github.com/marktsuchida/ssstr","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/marktsuchida/ssstr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marktsuchida%2Fssstr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marktsuchida%2Fssstr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marktsuchida%2Fssstr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marktsuchida%2Fssstr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marktsuchida","download_url":"https://codeload.github.com/marktsuchida/ssstr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marktsuchida%2Fssstr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34677382,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-23T02:00:07.161Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c","string","strings"],"created_at":"2024-11-09T06:27:38.635Z","updated_at":"2026-06-23T05:31:58.079Z","avatar_url":"https://github.com/marktsuchida.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!--\nThis file is part of the Ssstr string library.\nCopyright 2022-2023 Board of Regents of the University of Wisconsin System\nSPDX-License-Identifier: MIT\n--\u003e\n\n# Ssstr: a string library for C\n\n**Ssstr** is a header-only C library for byte string manipulation.\n\n```c\n#include \u003css8str.h\u003e\n```\n\n**Ssstr** fills the gap between raw `string.h` and heavyweight frameworks like\nGLib. It is useful for simple string processing: path names, command line\narguments, environment variables, and protocol messages.\n\nSee the [usage introduction](#usage) and\n[reference documentation](https://marktsuchida.github.io/ssstr/man7/ssstr.7.html).\n\n## Features\n\n- Analogues of `string.h` functions, plus C++ `std::string`-style conveniences,\n  all with automatic buffer management\n- Smooth interop with raw null-terminated strings and sized byte buffers;\n  supports embedded null bytes\n- Small string optimization (SSO): up to 31 bytes (64-bit) or 15 bytes (32-bit)\n  stored without heap allocation\n- Move and swap to minimize copying\n- Narrow contract API with optional debug assertions\n- Header-only, C99+, extensively tested, thoroughly\n  [documented](https://marktsuchida.github.io/ssstr/man7/ssstr.7.html)\n- Compiles without warnings (including MSVC without `_CRT_SECURE_NO_WARNINGS`)\n\n## Non-features\n\n**Ssstr** handles byte strings only: no character encoding support. UTF-8 works\nwell, but functions that split strings at arbitrary offsets (e.g.,\n`ss8_copy_substr()`, `ss8_replace()`) can break multi-byte sequences. Libraries\nlike [utf8proc](https://github.com/JuliaStrings/utf8proc) or\n[ICU](https://icu.unicode.org/) can complement **Ssstr** for Unicode\nprocessing.\n\nReference counting and copy-on-write are non-goals; each string is completely\nindependent.\n\n## Usage\n\nAPI reference is available as\n[manual pages](https://marktsuchida.github.io/ssstr/man7/ssstr.7.html).\n\n### String lifecycle\n\nStrings are represented by the type `ss8str`. An `ss8str` must be initialized\nbefore any use by passing its address to `ss8_init()`:\n\n\u003c!--\n%TEST_SNIPPET\n--\u003e\n\n```c\nss8str s;\nss8_init(\u0026s);\n```\n\n\u003c!--\n%SNIPPET_EPILOGUE ss8_destroy(\u0026s);\n--\u003e\n\nNote that directly assigning to, or initializing, an already-initialized\n`ss8str` results in undefined behavior.\n\nAn `ss8str` (which is the size of 4 pointers) is intended to be allocated on\nthe stack as a local variable unless it is part of a larger data structure.\n\nAfter use, an `ss8str` must be destroyed in order to release any dynamically\nallocated memory it may have used:\n\n\u003c!--\n%TEST_SNIPPET\n%SNIPPET_PROLOGUE ss8str s;\n%SNIPPET_PROLOGUE ss8_init(\u0026s);\n--\u003e\n\n```c\nss8_destroy(\u0026s);\n```\n\nAfter a call to `ss8_destroy()`, the `ss8str` is in an invalid state and cannot\nbe reused unless another call to `ss8_init()` is made. Calling `ss8_destroy()`\non an uninitialized (or already-destroyed) `ss8str` results in undefined\nbehavior.\n\n### Initializing a string with contents\n\nInstead of `ss8_init()`, you can use one of the other \"init\" functions to set\nthe string upon creation:\n\n\u003c!--\n%TEST_SNIPPET\n--\u003e\n\n```c\nss8str greeting, another, bytes012, comma, fortran_indent;\nss8_init_copy_cstr(\u0026greeting, \"Hello\");\nss8_init_copy(\u0026another, \u0026greeting);\nss8_init_copy_bytes(\u0026bytes012, \"\\0\\1\\2\", 3);\nss8_init_copy_ch(\u0026comma, ',');                // Set to \",\"\nss8_init_copy_ch_n(\u0026fortran_indent, ' ', 6);  // Set to \"      \"\n\n// ...\n\nss8_destroy(\u0026greeting);\nss8_destroy(\u0026another);\nss8_destroy(\u0026bytes012);\nss8_destroy(\u0026comma);\nss8_destroy(\u0026fortran_indent);\n```\n\n### Assigning to a string\n\nThere is a set of similar functions to set the contents of an existing (already\ninitialized) `ss8str`, overwriting whatever it contained:\n\n\u003c!--\n%TEST_SNIPPET\n--\u003e\n\n```c\nss8str s, t;\nss8_init(\u0026s);\nss8_init(\u0026t);\n\nss8_copy_cstr(\u0026s, \"Hello\");       // Set s to \"Hello\"\nss8_copy(\u0026t, \u0026s);                 // Set t to \"Hello\"\nss8_copy_bytes(\u0026s, \"\\0\\1\\2\", 3);  // Set s to \"\\0\\1\\2\"\nss8_copy_ch(\u0026s, ',');             // Set s to \",\"\nss8_copy_ch_n(\u0026s, ' ', 6);        // Set s to \"      \"\nss8_clear(\u0026s);                    // Set s to \"\"\n\n// ...\n\nss8_destroy(\u0026s);\nss8_destroy(\u0026t);\n```\n\n### Accessing contents\n\n\u003c!--\n%TEST_SNIPPET\n--\u003e\n\n```c\nss8str example;\nss8_init_copy_cstr(\u0026example, \"Hello\");\n\nsize_t len = ss8_len(\u0026example);             // 5\nbool e = ss8_is_empty(\u0026example);            // false\n\n// Pass to a function expecting a null-terminated string\nputs(ss8_cstr(\u0026example));\n\n// Pass a right substring (prints \"lo!\\n\")\nprintf(\"%s!\\n\", ss8_cstr_suffix(\u0026example, 3));\n\nchar ch = ss8_at(\u0026example, 1);              // 'e'\nchar first = ss8_front(\u0026example);           // 'H'\nchar last = ss8_back(\u0026example);             // 'o'\n\nss8_destroy(\u0026example);\n```\n\n\u003c!--\n%SNIPPET_EPILOGUE (void)len;\n%SNIPPET_EPILOGUE (void)e;\n%SNIPPET_EPILOGUE (void)ch;\n%SNIPPET_EPILOGUE (void)first;\n%SNIPPET_EPILOGUE (void)last;\n--\u003e\n\nNote that the result of `ss8_len(\u0026s)` and `strlen(ss8_cstr(\u0026s))` would differ\nif the string contained embedded null bytes.\n\nThe `ss8_cstr()` function returns a pointer to the string's internal buffer,\nwithout making a copy. The string is always null-terminated, whether or not it\ncontains embedded null bytes.\n\n### Mutating characters\n\n\u003c!--\n%TEST_SNIPPET\n--\u003e\n\n```c\nss8str s;\nss8_init_copy_cstr(\u0026s, \"ABCDE\");\nss8_set_at(\u0026s, 2, 'c');  // s is now \"ABcDE\"\nss8_set_front(\u0026s, 'a');  // s is now \"aBcDE\"\nss8_set_back(\u0026s, 'e');   // s is now \"aBcDe\"\nss8_destroy(\u0026s);\n```\n\n### Calling C APIs that produce a string\n\nMany C APIs write strings into caller-provided buffers, with varying\nconventions for signaling truncation. **Ssstr** provides functions to\ninteroperate with most of these.\n\nThe `ss8_set_len()` function adjusts the length of the string, leaving any new\nportion of the string uninitialized. This can be used to prepare a destination\nbuffer of the desired size.\n\n`ss8_mutable_cstr()`, the non-const version of `ss8_cstr()`, returns a pointer\nthat you can pass to functions that will write a string. There is also\n`ss8_mutable_cstr_suffix()` which you can use to append onto an existing\nstring.\n\nIf you adjusted the length of an `ss8str` using `ss8_set_len()`, you probably\nneed to readjust it after the call to the string-producing function, unless you\nknew beforehand the exact length. For this, you can call `ss8_set_len()` again\n(if the function returned the number of bytes written), or use\n`ss8_set_len_to_cstrlen()` (if the written string is null-terminated and\ncontains no embedded null bytes).\n\nSome APIs may not provide a way to determine the result length until you manage\nto pass a large-enough buffer. In this case, you can use `ss8_grow_len()` to\nprogressively increase the length of the string being used as the destination\nbuffer, calling the API function in a loop until you succeed. The\n`ss8_grow_len()` function is similar to `ss8_set_len()`, but automatically\nchooses a new length.\n\nAlso depending on the API, you may be required to pass either the _maximum\nstring length_ (not including any null terminator) or _destination buffer size_\n(including the null terminator). In the latter case, it is safe to pass\n`length + 1`, provided that the function will only ever write `\\0` to the byte\njust beyond the length of the `ss8str`. (But make sure to check that this is\nthe case; some functions, such as `strncpy()`, violate this requirement.)\n\n**Note:** Be sure not to confuse length and capacity. It is illegal to write\nbeyond the _length_ of an `ss8str` when using `ss8_mutable_cstr()` (aside from\nthe null terminator exception mentioned above), no matter how large its current\n_capacity_ may be.\n\n#### Example: Calling `strftime()`\n\nThe standard library function `strftime()` returns the number of bytes written\n(not including the null terminator) upon success, but does not provide a way to\ndetermine the necessary buffer size beforehand. It is therefore most correct to\ncall it with successively increasing buffer sizes:\n\n\u003c!--\n%TEST_SNIPPET\n--\u003e\n\n```c\n// UTC time when Earth was closest to the Sun in 2022.\nstruct tm perihelion = {\n    .tm_year = 122,  // 2022\n    .tm_mon = 0,     // January\n    .tm_mday = 4,    // 4th\n    .tm_hour = 7,\n    .tm_min = 10,\n};\n\nss8str datetime;\nss8_init(\u0026datetime);\n\nsize_t len = 0;\nwhile (!len) {\n    ss8_grow_len(\u0026datetime, SIZE_MAX, SIZE_MAX);\n    len = strftime(ss8_mutable_cstr(\u0026datetime), ss8_len(\u0026datetime) + 1,\n                   \"%Y-%m-%d %H:%M:%S UTC\", \u0026perihelion);\n}\nss8_set_len(\u0026datetime, len);\n\nprintf(\"%s\\n\", ss8_cstr(\u0026datetime));\n\nss8_destroy(\u0026datetime);\n```\n\n\u003c!--\n%SNIPPET_EPILOGUE TEST_ASSERT_EQUAL_size_t(23, len);\n--\u003e\n\nThis format produces a fixed 23-byte result, but the pattern applies to\nlocale-dependent formats too. `ss8_grow_len()`, starting with an empty\n`ss8str`, ensures the first iteration tries the maximum SSO length, avoiding\n`malloc()` on 64-bit platforms.\n\n### Copying to a plain C buffer\n\nConversely, you may have an `ss8str` and wish to write an API function for raw\nC string users. This is even easier:\n\n\u003c!--\n%TEST_SNIPPET COMPILE_ONLY FILE_SCOPE\n--\u003e\n\n```c\n// Returns length that would have been written given sufficient bufsize.\n// Can call with buf == NULL to determine result length.\nsize_t get_greeting(char *buf, size_t bufsize) {\n    // In real code, this string would come from elsewhere.\n    ss8str greeting;\n    ss8_init_copy_cstr(\u0026greeting, \"Hello, World!\");\n\n    if (buf \u0026\u0026 bufsize \u003e= 1)\n        ss8_copy_to_cstr(\u0026greeting, buf, bufsize);  // Copy portion that fits.\n\n    size_t ret = ss8_len(\u0026greeting);\n    ss8_destroy(\u0026greeting);\n    return ret;\n}\n```\n\nThere is also the variant `ss8_copy_to_bytes()`, which does the same thing as\n`ss8_copy_to_cstr()` but doesn't add a null terminator.\n\n### Concatenating and assembling strings\n\n\u003c!--\n%TEST_SNIPPET\n%SNIPPET_PROLOGUE ss8str dest, src;\n%SNIPPET_PROLOGUE ss8_init(\u0026dest);\n%SNIPPET_PROLOGUE ss8_init(\u0026src);\n%SNIPPET_PROLOGUE char const *cstr = \"\", *buf = \"\";\n%SNIPPET_PROLOGUE size_t len = 0, count = 0;\n--\u003e\n\n```c\nss8_cat(\u0026dest, \u0026src);\nss8_cat_cstr(\u0026dest, cstr);\nss8_cat_bytes(\u0026dest, buf, len);\nss8_cat_ch(\u0026dest, 'c');\nss8_cat_ch_n(\u0026dest, 'c', count);\n```\n\n\u003c!--\n%SNIPPET_EPILOGUE ss8_destroy(\u0026dest);\n%SNIPPET_EPILOGUE ss8_destroy(\u0026src);\n--\u003e\n\n#### Reserving space\n\nWhen assembling a string by concatenating multiple short strings, the `ss8str`\nmight need to reallocate its internal buffer multiple times to hold the growing\nstring. Memory allocation is slow, so this is done by enlarging the buffer\nexponentially by a constant factor (currently 1.5) to avoid excessively\nfrequent reallocations.\n\nHowever, if you know the exact or approximate final length of the string, it is\neven more efficient to allocate the required capacity upfront:\n\n\u003c!--\n%TEST_SNIPPET\n--\u003e\n\n```c\nss8str s;\nss8_init(\u0026s);\n\nss8_reserve(\u0026s, 40);  // Without changing length, enlarges buffer capacity.\nss8_copy_cstr(\u0026s, \"[INFO]\");\nss8_cat_ch(\u0026s, ' ');\nss8_cat_cstr(\u0026s, \"01:23:45\");\nss8_cat_ch(\u0026s, ' ');\nss8_cat_cstr(\u0026s, \"This is a log entry.\");\nss8_cat_ch(\u0026s, '\\n');\n\n// ...\n\nss8_destroy(\u0026s);\n```\n\nReserving space when the string is empty is most efficient because no existing\ndata needs to be copied (otherwise, the entire current capacity's worth of\nbytes may be copied, even if the string is shorter).\n\nYou can get the current capacity of an `ss8str` by calling `ss8_capacity()`,\nwhich returns the maximum number of bytes the string can contain without\nallocating new memory.\n\nOperations that cause the string to become shorter do not free the resulting\nunused memory. If you shorten a string by a large factor and want to ensure\nthat the unused memory is freed, you can call `ss8_shrink_to_fit()`. This may\nmake sense if a large number of such strings will be kept around for a long\ntime.\n\n### Chaining calls\n\nMost of the functions that take an `ss8str *` as the first argument and modify\nthe string also return the first argument. This can be used to chain multiple\nfunction calls (although it becomes nearly unreadable beyond 2 or 3 calls).\n\n\u003c!--\n%TEST_SNIPPET\n--\u003e\n\n```c\nchar const *heading = \"error: \", *message = strerror(ERANGE);\nss8str log_line;\nss8_cat_cstr(ss8_init_copy_cstr(\u0026log_line, heading), message);\n// ...\nss8_destroy(\u0026log_line);\n```\n\n### Moving strings around without copying\n\nYou might have an `ss8str` contained in some data structure (say, an array or\nlinked list element). If you want to set that string to one that you have in a\nlocal variable, but no longer need the local copy, you can do a swap to avoid\nmaking a copy of the whole string:\n\n\u003c!--\n%TEST_SNIPPET\n%SNIPPET_PROLOGUE ss8str s1, s2;\n%SNIPPET_PROLOGUE ss8_init(\u0026s1);\n%SNIPPET_PROLOGUE ss8_init(\u0026s2);\n--\u003e\n\n```c\n// Swaps the contents of the two strings s1 and s2:\nss8_swap(\u0026s1, \u0026s2);\n```\n\n\u003c!--\n%SNIPPET_EPILOGUE ss8_destroy(\u0026s1);\n%SNIPPET_EPILOGUE ss8_destroy(\u0026s2);\n--\u003e\n\nFor `ss8_swap()`, the two strings must not be the same `ss8str` object, or else\nundefined behavior will result. Also, the two `ss8str` objects must be valid\n(initialized); use `ss8_init_move_destroy()` to swap an uninitialized `ss8str`\nwith an initialized one.\n\nWhen the operation is asymmetric, i.e., you want to move the value of `s2` into\n`s1`, but do not care what `s2` holds afterwards, you can use `ss8_move()`:\n\n\u003c!--\n%TEST_SNIPPET\n%SNIPPET_PROLOGUE ss8str s1, s2, s3, s4;\n%SNIPPET_PROLOGUE ss8_init(\u0026s1);\n%SNIPPET_PROLOGUE ss8_init(\u0026s2);\n%SNIPPET_PROLOGUE ss8_init(\u0026s3);\n%SNIPPET_PROLOGUE ss8_init(\u0026s4);\n--\u003e\n\n```c\nss8_move(\u0026s1, \u0026s2);  // Moves value of s2 into s1\n// s2 remains valid (must be destroyed later), but its value is now\n// indeterminate\n\nss8_move_destroy(\u0026s3, \u0026s4);  // Moves value of s4 into s3\n// s4 is destroyed and must be initialized before reuse\n```\n\n\u003c!--\n%SNIPPET_EPILOGUE ss8_destroy(\u0026s1);\n%SNIPPET_EPILOGUE ss8_destroy(\u0026s2);\n%SNIPPET_EPILOGUE ss8_destroy(\u0026s3);\n--\u003e\n\nThere is also `ss8_init_move()` and `ss8_init_move_destroy()`.\n\n### Getting substrings\n\n\u003c!--\n%TEST_SNIPPET\n%SNIPPET_PROLOGUE ss8str dest, src, s;\n%SNIPPET_PROLOGUE ss8_init(\u0026dest);\n%SNIPPET_PROLOGUE ss8_init(\u0026src);\n%SNIPPET_PROLOGUE ss8_init(\u0026s);\n%SNIPPET_PROLOGUE size_t start = 0, len = 0;\n--\u003e\n\n```c\nss8_copy_substr(\u0026dest, \u0026src, start, len);\nss8_substr_inplace(\u0026s, start, len);\n```\n\n\u003c!--\n%SNIPPET_EPILOGUE ss8_destroy(\u0026dest);\n%SNIPPET_EPILOGUE ss8_destroy(\u0026src);\n%SNIPPET_EPILOGUE ss8_destroy(\u0026s);\n--\u003e\n\n### Inserting, erasing, and replacing\n\n\u003c!--\n%TEST_SNIPPET\n%SNIPPET_PROLOGUE ss8str dest, src;\n%SNIPPET_PROLOGUE ss8_init(\u0026dest);\n%SNIPPET_PROLOGUE ss8_init(\u0026src);\n%SNIPPET_PROLOGUE char const *cstr = \"\", *buf = \"\";\n%SNIPPET_PROLOGUE size_t pos = 0, len = 0, buflen = 0, count = 0;\n--\u003e\n\n```c\n// Insert the given string or char(s) at the given position\nss8_insert(\u0026dest, pos, \u0026src);\nss8_insert_cstr(\u0026dest, pos, cstr);\nss8_insert_bytes(\u0026dest, pos, buf, buflen);\nss8_insert_ch(\u0026dest, pos, 'c');\nss8_insert_ch_n(\u0026dest, pos, 'c', count);\n\n// Remove the given range\nss8_erase(\u0026dest, pos, len);\n\n// Replace the given range with the given string\nss8_replace(\u0026dest, pos, len, \u0026src);\nss8_replace_cstr(\u0026dest, pos, len, cstr);\nss8_replace_bytes(\u0026dest, pos, len, buf, buflen);\nss8_replace_ch(\u0026dest, pos, len, 'c');\nss8_replace_ch_n(\u0026dest, pos, len, 'c', count);\n```\n\n\u003c!--\n%SNIPPET_EPILOGUE ss8_destroy(\u0026dest);\n%SNIPPET_EPILOGUE ss8_destroy(\u0026src);\n--\u003e\n\n### Comparing strings\n\n\u003c!--\n%TEST_SNIPPET\n%SNIPPET_PROLOGUE ss8str lhs, rhs, s, prefix, suffix, infix;\n%SNIPPET_PROLOGUE ss8_init(\u0026lhs);\n%SNIPPET_PROLOGUE ss8_init(\u0026rhs);\n%SNIPPET_PROLOGUE ss8_init(\u0026s);\n%SNIPPET_PROLOGUE ss8_init(\u0026prefix);\n%SNIPPET_PROLOGUE ss8_init(\u0026suffix);\n%SNIPPET_PROLOGUE ss8_init(\u0026infix);\n%SNIPPET_PROLOGUE char const *cstr = \"\", *buf = \"\";\n%SNIPPET_PROLOGUE size_t len = 0;\n--\u003e\n\n```c\n// Return \u003c0, ==0, or \u003e0, as with strcmp() or memcmp():\nss8_cmp(\u0026lhs, \u0026rhs);\nss8_cmp_cstr(\u0026lhs, cstr);\nss8_cmp_bytes(\u0026lhs, buf, len);\nss8_cmp_ch(\u0026lhs, 'c');\n\n// Boolean check for equality:\nss8_equals(\u0026lhs, \u0026rhs);\nss8_equals_cstr(\u0026lhs, cstr);\nss8_equals_bytes(\u0026lhs, buf, len);\nss8_equals_ch(\u0026lhs, 'c');\n\n// Boolean check for prefix match:\nss8_starts_with(\u0026s, \u0026prefix);\nss8_starts_with_cstr(\u0026s, cstr);\nss8_starts_with_bytes(\u0026s, buf, len);\nss8_starts_with_ch(\u0026s, 'c');\n\n// Boolean check for suffix match:\nss8_ends_with(\u0026s, \u0026suffix);\nss8_ends_with_cstr(\u0026s, cstr);\nss8_ends_with_bytes(\u0026s, buf, len);\nss8_ends_with_ch(\u0026s, 'c');\n\n// Boolean check for substring match:\nss8_contains(\u0026s, \u0026infix);\nss8_contains_cstr(\u0026s, cstr);\nss8_contains_bytes(\u0026s, buf, len);\nss8_contains_ch(\u0026s, 'c');\n```\n\n\u003c!--\n%SNIPPET_EPILOGUE ss8_destroy(\u0026lhs);\n%SNIPPET_EPILOGUE ss8_destroy(\u0026rhs);\n%SNIPPET_EPILOGUE ss8_destroy(\u0026s);\n%SNIPPET_EPILOGUE ss8_destroy(\u0026prefix);\n%SNIPPET_EPILOGUE ss8_destroy(\u0026suffix);\n%SNIPPET_EPILOGUE ss8_destroy(\u0026infix);\n--\u003e\n\n### Searching strings\n\n\u003c!--\n%TEST_SNIPPET\n%SNIPPET_PROLOGUE ss8str haystack, needle, needles;\n%SNIPPET_PROLOGUE ss8_init(\u0026haystack);\n%SNIPPET_PROLOGUE ss8_init(\u0026needle);\n%SNIPPET_PROLOGUE ss8_init(\u0026needles);\n%SNIPPET_PROLOGUE char *cstr = \"\", *buf = \"\";\n%SNIPPET_PROLOGUE size_t start = 0, len = 0;\n--\u003e\n\n```c\n// Search forward from start; return position, or SIZE_MAX if not found:\nss8_find(\u0026haystack, start, \u0026needle);\nss8_find_cstr(\u0026haystack, start, cstr);\nss8_find_bytes(\u0026haystack, start, buf, len);\nss8_find_ch(\u0026haystack, start, 'c');\nss8_find_not_ch(\u0026haystack, start, 'c');\n\n// Search backward from start; return position, or SIZE_MAX if not found:\nss8_rfind(\u0026haystack, start, \u0026needle);\nss8_rfind_cstr(\u0026haystack, start, cstr);\nss8_rfind_bytes(\u0026haystack, start, buf, len);\nss8_rfind_ch(\u0026haystack, start, 'c');\nss8_rfind_not_ch(\u0026haystack, start, 'c');\n\n// Search forward for any char in 'needles':\nss8_find_first_of(\u0026haystack, start, \u0026needles);\nss8_find_first_of_cstr(\u0026haystack, start, cstr);\nss8_find_first_of_bytes(\u0026haystack, start, buf, len);\n\n// Search forward for any char not in 'needles':\nss8_find_first_not_of(\u0026haystack, start, \u0026needles);\nss8_find_first_not_of_cstr(\u0026haystack, start, cstr);\nss8_find_first_not_of_bytes(\u0026haystack, start, buf, len);\n\n// Search backward for any char in 'needles':\nss8_find_last_of(\u0026haystack, start, \u0026needles);\nss8_find_last_of_cstr(\u0026haystack, start, cstr);\nss8_find_last_of_bytes(\u0026haystack, start, buf, len);\n\n// Search backward for any char not in 'needles':\nss8_find_last_not_of(\u0026haystack, start, \u0026needles);\nss8_find_last_not_of_cstr(\u0026haystack, start, cstr);\nss8_find_last_not_of_bytes(\u0026haystack, start, buf, len);\n```\n\n\u003c!--\n%SNIPPET_EPILOGUE ss8_destroy(\u0026haystack);\n%SNIPPET_EPILOGUE ss8_destroy(\u0026needle);\n%SNIPPET_EPILOGUE ss8_destroy(\u0026needles);\n--\u003e\n\n### Stripping characters off the ends\n\n\u003c!--\n%TEST_SNIPPET\n%SNIPPET_PROLOGUE ss8str s, chars;\n%SNIPPET_PROLOGUE ss8_init(\u0026s);\n%SNIPPET_PROLOGUE ss8_init(\u0026chars);\n%SNIPPET_PROLOGUE char *cstr = \"\", *buf = \"\";\n%SNIPPET_PROLOGUE size_t len = 0;\n--\u003e\n\n```c\n// Remove any characters in 'chars' from either end of s:\nss8_strip(\u0026s, \u0026chars);\nss8_strip_cstr(\u0026s, cstr);\nss8_strip_bytes(\u0026s, buf, len);\nss8_strip_ch(\u0026s, 'c');\n\n// Remove any characters in 'chars' from the beginning of s:\nss8_lstrip(\u0026s, \u0026chars);\nss8_lstrip_cstr(\u0026s, cstr);\nss8_lstrip_bytes(\u0026s, buf, len);\nss8_lstrip_ch(\u0026s, 'c');\n\n// Remove any characters in 'chars' from the end of s:\nss8_rstrip(\u0026s, \u0026chars);\nss8_rstrip_cstr(\u0026s, cstr);\nss8_rstrip_bytes(\u0026s, buf, len);\nss8_rstrip_ch(\u0026s, 'c');\n```\n\n\u003c!--\n%SNIPPET_EPILOGUE ss8_destroy(\u0026s);\n%SNIPPET_EPILOGUE ss8_destroy(\u0026chars);\n--\u003e\n\n### Formatting strings\n\n```c\n// Format string\nss8_sprintf(\u0026dest, \"fmt\", ...);\nss8_snprintf(\u0026dest, maxlen, \"fmt\", ...);\n\n// Append formatted string\nss8_cat_sprintf(\u0026dest, \"fmt\", ...);\nss8_cat_snprintf(\u0026dest, maxlen, \"fmt\", ...);\n\n// Versions taking va_list\nss8_vsprintf(\u0026dest, \"fmt\", args);\nss8_vsnprintf(\u0026dest, maxlen, \"fmt\", args);\nss8_cat_vsprintf(\u0026dest, \"fmt\", args);\nss8_cat_vsnprintf(\u0026dest, maxlen, \"fmt\", args);\n```\n\nThese functions internally call `vsnprintf()`, so the format string has the\nsame meaning as the `printf()` family of functions provided by the standard\nlibrary (and is therefore platform-dependent to some extent).\n\nIn particular, any `\"%s\"` format specifier requires a corresponding standard\nnull-terminated string, so you need to use `ss8_cstr()` if printing an\n`ss8str`:\n\n\u003c!--\n%TEST_SNIPPET\n--\u003e\n\n```c\nss8str warning, dest;\nss8_init_copy_cstr(\u0026warning, \"I'm not a C string\");\nss8_init(\u0026dest);\nss8_sprintf(\u0026dest, \"warning: %s (%d)\", ss8_cstr(\u0026warning), 42);\n// ...\nss8_destroy(\u0026dest);\nss8_destroy(\u0026warning);\n```\n\n## Installing Ssstr\n\nYou do not need to install **Ssstr**; simply copy `include/ss8str.h` into your\nproject. But installing into a system (or custom) location may be useful in\nsome use cases.\n\nTo install the header (along with pkg-config file and CMake config):\n\nRequirements: [Meson](https://mesonbuild.com) and\n[Ninja](https://ninja-build.org/).\n\n```sh\nmeson setup builddir\ncd builddir\nninja install\n```\n\nNotes:\n\n- Use `meson setup builddir --prefix=/path/to/install/root` to set the install\n  location (default: `/usr/local`, or `C:/` on Windows).\n- Use `meson setup builddir -Ddocs=enabled` to also build and install the man\n  pages (`groff` and [`uv`](https://docs.astral.sh/uv/) are required).\n- The CMake config is only installed if `cmake` is available.\n\n### Using from a Meson project\n\nTo use **Ssstr** as a subproject, you can place the following in\n`subprojects/ssstr.wrap` (setting the `revision` to an appropriate commit\nhash):\n\n```ini\n[wrap-git]\nurl = https://github.com/marktsuchida/ssstr.git\nrevision = \u003cyour choice\u003e\ndepth = 1\n\n[provide]\ndependency_names = ssstr\n```\n\nThen, in your `meson.build`, use\n`ssstr_dep = dependency('ssstr', allow_fallback: true)`\n\nAlternatively, you could [install](#installing-ssstr) **Ssstr** and let Meson\nfind the dependency via pkg-config.\n\n### Using from a CMake project\n\nFirst, [install](#installing-ssstr) **Ssstr**. This should install\n`${prefix}/lib/cmake/Ssstr/SsstrConfig.cmake`.\n\nThen, in your `CMakeLists.txt`, use something like the following:\n\n```cmake\nfind_package(Ssstr REQUIRED)\nadd_executable(my_program main.c)\ntarget_link_libraries(my_program PRIVATE Ssstr::ssstr)\n```\n\n(Despite the use of `target_link_libraries`, only the include directory is\nactually provided to `my_program`.)\n\nWhen running `cmake`, you can point it to the **Ssstr** installation by passing\n`-DSsstr_DIR=${prefix}/lib/cmake/Ssstr`.\n\n## Running the tests\n\nRequirements: [Python](https://python.org), [Meson](https://mesonbuild.com),\nand [Ninja](https://ninja-build.org/).\n\n```sh\nmeson setup builddir -Dtest=enabled\ncd builddir\nninja test\n```\n\nOther build targets:\n\n- `ninja benchmark` (requires `meson configure -Dbenchmark=enabled`)\n- `ninja htmlman` (requires `groff`, [`uv`](https://docs.astral.sh/uv/), and\n  `meson configure -Ddocs=enabled`) generates the HTML manual pages\n\n### Code coverage\n\nTest coverage information can be generated as follows (requires gcovr or\nsimilar):\n\n```sh\ncd builddir\nmeson configure -Db_coverage=true\nninja clean  # Ensure no previous coverage data remains\nninja test\nninja coverage-html\n# Now open meson-logs/coveragereport/index.html\n```\n\nCurrently, some of the branch coverage is inaccurate because the definition of\n`SSSTR_ASSERT` differs between tests.\n\nI try to maintain near-perfect coverage for the functions and branches in\n`ss8str.h`, with the exception of codepaths leading to panics, compile-time\ndisabled code, and (very few) codepaths that cannot be tested without buffers\nsized near `INT_MAX` or larger. However, unit tests should strive to test as\nmany edge cases as possible, not merely exercise every line of code.\n\n## Customization\n\nA few aspects of **Ssstr** can be customized by defining preprocessor macros\n_before_ including `ss8str.h`. All of them (except `SSSTR_EXTRA_DEBUG`) are\nadvanced features.\n\n### Avoiding `static inline` functions\n\nBy default, all **Ssstr** functions are `static inline`, so that simply\nincluding `ss8str.h` is all you need to do. However, if you include the header\nfrom multiple translation units (source files), each will get their own copy of\nthose **Ssstr** functions which the compiler chose not to inline. This may not\nbe desirable in projects where small binary size is important.\n\n(Note that toolchain facilities such as GCC/Clang `-fipa-icf` (enabled as part\nof `-O2`; most effective when combined with link-time optimization) and MSVC\nidentical COMDAT folding can alleviate this, but there may be cases where that\nis not good enough.)\n\nIf the macro `SSSTR_USE_NONSTATIC_INLINE` is defined, **Ssstr** functions will\nbe defined as plain `inline`, so that duplicate copies of the functions will\nnot be generated. Because of the way\n[C inline functions](https://en.cppreference.com/w/c/language/inline) work\n(which differs from C++), the macro `SSSTR_DEFINE_EXTERN_INLINE` must also be\ndefined in exactly one translation unit; this provides `extern inline` function\ndefinitions.\n\n### Customizing memory allocation\n\nBy default, **Ssstr** uses the standard library functions `malloc()`,\n`realloc()`, and `free()` to manage dynamic storage. If you want **Ssstr** to\ninstead use a different allocator, you can define the function-style macros\n`SSSTR_MALLOC(size)`, `SSSTR_REALLOC(ptr, size)`, and `SSSTR_FREE(ptr)`.\n\nNo **Ssstr** function calls `SSSTR_MALLOC()` or `SSSTR_REALLOC()` with a `size`\nof zero or a null `ptr`, so your definitions need not handle these edge cases\nin any specific manner. Also, **Ssstr** always checks the return value for\n`NULL` (see below on error handling).\n\nIf you customize memory allocation, you are responsible for ensuring that\ncompatible customizations are used throughout your program (or at least\nthroughout the subsystem within which a given set of `ss8str` objects are\npassed around).\n\n### Customizing run-time assertions\n\n**Ssstr** calls the standard `assert()` macro if there is a precondition\nviolation (that is, programming error in user code). You can replace this\nbehavior by defining the macro `SSSTR_ASSERT(condition)`.\n\nTo disable assertions, you should define `NDEBUG`; there is no need to define\n`SSSTR_ASSERT` just for this purpose.\n\n`SSSTR_ASSERT()` should not return when the condition is false (if it checks\nthe condition at all). It is safe to call `longjmp()` from `SSSTR_ASSERT()`\nwhen the condition is false.\n\n### Enabling more thorough run-time checks\n\nFor performance reasons, not all detectable precondition violations are checked\nby default. In a debug build, you can enable extra assertions and debugging\nfeatures by defining the macro `SSSTR_EXTRA_DEBUG`.\n\nSome of the violations that may be caught include null pointers passed as\narguments, overlapping buffers (where not allowed), and (with some luck)\ncorrupted `ss8str` objects.\n\nIn addition, when built with `SSSTR_EXTRA_DEBUG` defined, the portion of a\nstring extended by `ss8_set_len()` or `ss8_grow_len()` is filled with `'~'`,\nand the right-hand-side of `ss8_move()` or `ss8_init_move()` is set to the\nstring `\"!!! MOVED-FROM SS8STR !!!\"`. These behaviors are intended to increase\nthe likelihood of spotting bugs due to erroneous access to indeterminate data.\n\n### Customizing error handling\n\nBy default, if memory allocation fails or if the size of a result is computed\nto be larger than `size_t` can express, **Ssstr** will print a message to\n`stderr` and call `abort()`.\n\nYou can customize this behavior by defining the macros\n`SSSTR_OUT_OF_MEMORY(bytes)` and `SSSTR_SIZE_OVERFLOW()`.\n\n`SSSTR_OUT_OF_MEMORY()` will be called if `SSSTR_MALLOC()` or `SSSTR_REALLOC()`\n(or their default equivalents) return `NULL`. The number of bytes that\n**Ssstr** attempted to allocate is passed as the argument.\n\n`SSSTR_SIZE_OVERFLOW()` will be called if the result of a string operation\n(such as concatenation, insertion, or replacement) would have a length greater\nthan `SIZE_MAX - 1`.\n\nIt is meant to be safe to call `longjmp()` from inside these 2 macros, but this\nhas not been tested.\n\nNote that `ss8_[cat_][v]s[n]printf()` will call `abort()` if the result exceeds\n`INT_MAX` bytes or `vs[n]printf()` returns a negative value. These errors are\nnot customizable. For robust string formatting, consider a dedicated library.\n\n## Memory layout\n\nAn `ss8str` occupies 32 bytes on 64-bit platforms. Depending on the current\ncapacity (not including null terminator), one of two layouts is used. Capacity\nis never less than 31.\n\nSmall string (capacity = 31; length \\\u003c= 31):\n\n```text\n+----------------------------------------------------+-----+\n| Bytes 0-30: string buffer (always null-terminated) |  31 |\n+----------------------------------------------------+-----+\n| H e l l o ,   W o r l d ! \\0                       | (*) |\n+----------------------------------------------------+-----+\n```\n\nByte 31 (`(*)`) is set to `31 - length`, which doubles as the null terminator\nwhen the length is exactly 31. (This idea comes from\n[`folly::fbstring`](https://github.com/facebook/folly/blob/main/folly/docs/FBString.md)\n([video](https://www.youtube.com/watch?v=kPR8h4-qZdk\u0026t=8s)), although that\nimplementation uses a 24 byte layout, requiring endian-specific manipulation.)\n\nLarge string (capacity \u003e 31; any length):\n\n```text\n+-------------+-------------+-------------+-----------+----+\n|  Bytes 0-7  |     8-15    |    16-23    |   24-30   | 31 |\n+-------------+-------------+-------------+-----------+----+\n|     ptr     |    length   |   bufsize   |  padding  | FF |\n+-------------+-------------+-------------+-----------+----+\n```\n\nDynamically allocated memory at `ptr` stores the null-terminated string.\nCapacity is `bufsize - 1` (for null terminator). Byte 31 always contains `0xFF`\nto distinguish from small strings.\n\nOn 32-bit platforms, an `ss8str` occupies 16 bytes and the capacity is never\nless than 15.\n\nIn principle, any size greater than or equal to 3 pointers could be chosen for\na string type with small string optimization. Larger choices will increase the\nchance of a string fitting in the small string buffer (avoiding dynamic\nallocation), but will waste space if many of the strings are either very short\nor longer than the small string capacity.\n\nA design with 3-pointer-sized string objects requires one of two compromises to\nbe made: either the small string buffer needs to be limited to the size of 2\npointers, or complicated techniques need to be used to encode the long/short\ndistinction into one of the 3 pointer/size fields (this is especially tricky in\nthe case of 32-bit architectures). The former approach is typical in C++\n`std::string` implementations, and the latter is used by the above-mentioned\n`folly::fbstring`.\n\nThe 4-pointer size of `ss8str` was chosen because it is the smallest that can\nbe achieved while having a single, simple implementation supporting little- and\nbig-endian, 32- and 64-bit architectures and with maximum utilization of the\nsmall string buffer.\n\nThe use cases for which **Ssstr** was designed are the handling of small\nnumbers (at a time) of short- to moderate-length strings—_not_ building\ndatabases or text editors. Applications that want to store large numbers of\nstrings with high space efficiency may prefer other designs; see, for example,\n[SDS](https://github.com/antirez/sds).\n\n### Buffer alignment\n\nAs a consequence of the memory layout, the beginning of the string buffer is\nalways aligned to the size of a pointer. This means that `ss8str` can be used\nto store strings in multibyte encodings (including UTF-16, UTF-32), provided\nthat special care is taken when manipulating (especially splitting) such\nstrings.\n\n## Versioning\n\n**Ssstr** intends to use [Semantic Versioning](https://semver.org/). The API,\nfor versioning purposes, consists of the functions and data types documented in\nthe [manual pages](https://marktsuchida.github.io/ssstr/man7/ssstr.7.html),\nplus the [customization macros](#customization) documented above.\n\nABI compatibility will be maintained more strictly: the memory layout of the\n`ss8str` object will not change on a given platform (if it ever does, the type,\nheader, and library will be renamed). This will apply to any version starting\nwith the 1.0.0 release; until then, things might change (though unlikely).\n\n(Note, however, that true binary compatibility requires that all copies of\n**Ssstr** code that exchange (mutable) `ss8str` objects be built with\ncompatible C runtimes, use the same dynamic storage heap, and have mutually\ncompatible malloc customizations, if any. For this reason, you might want to\nstick to traditional C string interfaces at your major module boundaries, or at\nleast limit the exchange of `ss8str` to `const ss8str *`, so that memory\nallocation and deallocation is limited to one side of the boundary.)\n\n## License\n\n**Ssstr** is distributed under the MIT license. See `LICENSE.txt`.\n\n## Other simple string libraries for C\n\n- [SDS](https://github.com/antirez/sds) (Simple Dynamic Strings)\n- [bstring](http://bstring.sourceforge.net/)\n  ([GitHub](https://github.com/websnarf/bstrlib)) (Better String Library)\n- utstring, part of [uthash](https://troydhanson.github.io/uthash/)\n  ([GitHub](https://github.com/troydhanson/uthash))\n- [str](https://github.com/maxim2266/str)\n\nIt is difficult to search for string libraries in C (too much noise), so I\nwouldn't be surprised if there are other good or notable ones that I am not\naware of. Also, several larger frameworks (e.g.\n[GLib](https://docs.gtk.org/glib/struct.String.html)) have string types.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarktsuchida%2Fssstr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarktsuchida%2Fssstr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarktsuchida%2Fssstr/lists"}