Skip to content

Commit 810307c

Browse files
4.0.1-alpha1 - hashing API; Wide-string CI (#42)
* Performance tests (#41) * fix(performance tests): "assign_len_grow" now more realistic * fix(performance tests): "create_destroy_empty" now more realistic * fix(performance tests): "append_len_growth_x" now more realistic * fix(performance tests): "append_len_reserved" now more realistic * Revert "fix(performance tests): "create_destroy_empty" now more realistic" This reverts commit 5998491. * fix(performance tests): "create_destroy_empty" std::string now constructs "" The cstring row calls cstring_create(&s, "") and raw_realloc assigns an empty C string. Default-constructing std::string skipped that step, so the row compared a materialised empty string with an untouched default object. Construct std::string from "" so all three sides perform the same creation. Small-string storage can still make std::string cheaper; that gap is the library. * fix(performance tests): assign and copy anchors read the first byte assign_len_grow and copy returned only the length, which the compiler can treat as invariant, so it deleted the visible raw_realloc memcpy. Rows from 256 bytes upward stayed near 12 ns with p50 of 0. Each implementation now adds the first payload byte to the anchor, as create_destroy_len already does, including the Windows arena lambdas. * fix(performance tests): append and insert anchors read the first byte append_len_growth, append_len_reserved, and insert_len_mid returned only the length, which the compiler can treat as invariant, so it can delete the visible raw_realloc copies. Each implementation now adds the first payload byte to the anchor, as assign_len_grow and copy already do, including the Windows arena lambdas. Every size in these scenarios leaves a non-empty string, so the first byte is in range. * fix(unit tests): COM initialisation * fix(performance tests): append_batch anchor reads stored payload append_batch returned only the vector length, which is the constant element count, so the compiler can delete the visible vector<string> insert. The payload-16 row printed 0 ns/op. Each implementation now takes the address of the first and last stored string through a volatile pointer and adds the first byte of each. That address is the copy, not the source, so those two strings have to be materialised. * fix(performance tests): one-by-one vector anchors read stored payload append_one_by_one and prepend_one_by_one returned only the vector length, which is the constant element count, so the compiler can delete the visible vector<string> updates. Each implementation now takes the address of the first and last stored string through a volatile pointer and adds the first byte of each, as append_batch already does. create_destroy is unchanged: its elements are empty, so there is no stored payload byte to read. * fix(performance tests): Windows assign/append ratios use createEx realloc On Windows, assign_len_grow, append_len_growth, and append_len_reserved arena rows call createEx("") before the mutation. The printed cstring row does not. Those arena ratios now use cstring_win_realloc, which runs the same body with CSTRING_F_USE_REALLOC. std::string and raw_realloc stay against the cstring row that skips the empty create. create, insert, and copy are unchanged: their cstring row already matches the arena body. * fix(performance tests): borrowed rows time a prebuilt buffer borrowed_fixed_construct and borrowed_fixed_assign built the caller buffer inside the timed loop, so sizes above 511 bytes included a heap allocation in the borrowed and fixed_char_buf times. The buffer is now built once per size and reused. std::string on those rows is an owning construct, and its ratio column is "-" rather than a comparison with cstring_borrowed. fixed_char_buf remains the like-for-like peer. * fix(performance tests): file-line peers share an owning anchor file_read_all_lines compared an owning fgetc reader with a getline loop that discarded its buffer on every move, and with platformstl::file_lines, which stores views over one copy of the file. getline now copies each line and clear()s the scratch string, and the stream is opened binary. All three anchors are the line count plus the sum of the line lengths, and a mismatch drops the rows. file_lines is still timed, and its ratio column is "-" because it does not own each line. ifstream+getline remains the owning peer. * fix(performance tests): dash the ratio when a row is elided When p50 is 0 and ns/op has not risen against the previous smaller size of the same scenario and implementation, vs cstr is "-". A deleted raw_realloc copy was printing a falling ratio (down to 0.02) while the time stayed flat. The first such size is dashed as well. * fix(performance tests): Windows copy calls cstring_copy The Windows copy rows built the destination with createLenEx of the source bytes, so they never called cstring_copy. They now createEx("") to select the arena, then cstring_copy, which allocates with the destination flags. The printed cstring row does not createEx(""), so those arena ratios use cstring_win_realloc. std::string and raw_realloc stay on the cstring row. * fix(performance tests): raw vector create leaves slots empty create_destroy's raw floor reserved the pointer array, then calloc'd a byte per slot. cstring_vector_create and vector<string>(n) only construct empty elements, with no character buffer. The raw slots are now null. Destroy still frees them, and free(NULL) is a no-op. * fix(performance tests): fgets honours the readline instance mode read_all_fgets always used one buffer of line_len+4, so fresh and pre-reserved did not differ and reuse never split a long line. Fresh now allocates that buffer per line, pre-reserved keeps one, and reuse keeps a 4096-byte buffer and stitches a short read, including a CR held across a chunk boundary. Anchors stay the line length plus the first content byte. * fix(performance tests): time ns/op as one interval When p99 is linked, ns/op was the sum of per-call samples, and each sample included start/stop. On short rows that overhead flattened the ratio. ns/op and vs cstr now use one start/stop around the iteration loop. Percentiles still come from a per-call pass on the recorded warmup, and that pass is not added to ns/op. * fix(performance tests): label percentiles as per iteration p50, p90, p99, and max are the median and tails of one iteration. ns/op divides the batch by #acts, so an append or readline percentile was the whole trial while ns/op was one append or one line. The columns are now p50/iter, p90/iter, p99/iter, and max/iter. The values stay one iteration. * fix(performance tests): prepend at payload 256 as well as 16 prepend_one_by_one ran only at payload 16, where vector<string> is still inside small-string storage. It now also runs at payload 256 and still prepends 32 elements, beside the append rows. The 16-byte row remains. * Hashing API (#19) * general boilerplate / project-support improvements (#16) * squash-commit * 4.0.14 * fix * .vscode/settings.json * Add cstring_hash_djb2() and cstring_hash_fnv1a() hashing API and test suite * Added 64-bit djb2 and FNV-1a hash functions for cstring_t instances and slices (cstring_hash_djb2, cstring_hash_djb2_ci, cstring_hash_djb2_len, cstring_hash_djb2_len_ci, cstring_hash_fnv1a, cstring_hash_fnv1a_ci, cstring_hash_fnv1a_len, cstring_hash_fnv1a_len_ci); * Added cstring_hash_t typedef (uint64_t) with fallback for older MSVC; * Added C++ inline access shims for instances and buffer slices; * Added comprehensive xTests unit test suite test.unit.cstring.hash; * Verified both narrow-character and wide-character build paths; * Documented hashing API, types, and C++ access shims in README.md; * Bumped version to 4.1.0-alpha1 and updated CHANGES.md and NEWS.md; * 4.0.15 (#20) * Updated all `\param` and `\retval` Doxygen documentation comments in `cstring.h`, `cstring.vector.h`, and `cstring.core.c` to obey the 76-rule and terminate with a semicolon; * Bumped version to 4.0.15 in `cstring.h`, `Doxyfile`, `CHANGES.md`, and `NEWS.md`; * Updated `Updated:` header banners to 6th September 2026; * squash-commit * NEWS.md * chore: renamed **test/unit/test.unit.cstring.hash/entry.cpp** => **test/unit/hash/entry.cpp** * chore --------- Co-authored-by: synesissoftware <matthew@synesis.com.au> * Hash review (#44) * chore(misc): language discrimination; CI branch(es) * chore(misc): ensuring `\file` designators are correct * chore(mist): removed unused `CSTRING_VECTOR_ASSERT()` * chore(misc): tidying * fix(cstring): discriminate cstring_hash_t and use it for hashing Gate <stdint.h> on C99, on C++ with that header or C++11, and on MSVC 7.1 or later, and typedef cstring_hash_t to uint64_t when it is included; use cstring_hash_t for the djb2 and FNV-1a API and for the hash unit tests. * chore(performance tests): prefer `uint64_t` to `std::uint64_t` * test(cstring): exercise the hashing API from a C unit test Move the eight C hashing functions into **test.unit.hash** (`entry.c`) so the suite builds when `NO_CSTRING_CPP_API` is on. Keep the C++ shim checks in **test.unit.hash.cxx**, which stays behind that option. * test(cstring): lock high-byte and embedded-NUL hash results Cover octet 0xFF and an interior NUL in test.unit.hash. Exact narrow vectors stay off the wide build so a later wchar_t fix is not frozen. * feat(cstring): publish the djb2 and FNV-1a basis macros Add CSTRING_HASH_DJB2_SEED, CSTRING_HASH_FNV1A_OFFSET, and CSTRING_HASH_FNV1A_PRIME beside cstring_hash_t, and use them as the only copies of those constants in the hash implementation. Cite Yigit's djb2 page and the Fowler, Noll, and Vo notes and RFC 9923, and lock each macro to its historic literal in the C unit test. * fix * refactor(cstring): rename the case-folded hash suffix to _case Rename the case-folded hash functions from the `_ci` suffix to `_case` (`cstring_hash_djb2_case`, `cstring_hash_djb2_len_case`, and the FNV-1a twins), including the C++ shims and the unit tests. * refactor(cstring): rename the counted hash suffix from _len to _buf Rename the pointer-and-length hash functions from `_len` to `_buf` (`cstring_hash_djb2_buf`, `cstring_hash_djb2_buf_case`, and the FNV-1a twins), including the C++ shims and the unit tests. * fix * Hash API (redux) (#45) * chore(misc): language discrimination; CI branch(es) * chore(misc): ensuring `\file` designators are correct * chore(mist): removed unused `CSTRING_VECTOR_ASSERT()` * chore(misc): tidying * fix(cstring): discriminate cstring_hash_t and use it for hashing Gate <stdint.h> on C99, on C++ with that header or C++11, and on MSVC 7.1 or later, and typedef cstring_hash_t to uint64_t when it is included; use cstring_hash_t for the djb2 and FNV-1a API and for the hash unit tests. * chore(performance tests): prefer `uint64_t` to `std::uint64_t` * test(cstring): exercise the hashing API from a C unit test Move the eight C hashing functions into **test.unit.hash** (`entry.c`) so the suite builds when `NO_CSTRING_CPP_API` is on. Keep the C++ shim checks in **test.unit.hash.cxx**, which stays behind that option. * test(cstring): lock high-byte and embedded-NUL hash results Cover octet 0xFF and an interior NUL in test.unit.hash. Exact narrow vectors stay off the wide build so a later wchar_t fix is not frozen. * feat(cstring): publish the djb2 and FNV-1a basis macros Add CSTRING_HASH_DJB2_SEED, CSTRING_HASH_FNV1A_OFFSET, and CSTRING_HASH_FNV1A_PRIME beside cstring_hash_t, and use them as the only copies of those constants in the hash implementation. Cite Yigit's djb2 page and the Fowler, Noll, and Vo notes and RFC 9923, and lock each macro to its historic literal in the C unit test. * fix * refactor(cstring): rename the case-folded hash suffix to _case Rename the case-folded hash functions from the `_ci` suffix to `_case` (`cstring_hash_djb2_case`, `cstring_hash_djb2_len_case`, and the FNV-1a twins), including the C++ shims and the unit tests. * refactor(cstring): rename the counted hash suffix from _len to _buf Rename the pointer-and-length hash functions from `_len` to `_buf` (`cstring_hash_djb2_buf`, `cstring_hash_djb2_buf_case`, and the FNV-1a twins), including the C++ shims and the unit tests. * refactor(cstring): .DEF file renames of hash functions * feat(cstring): add always-available multibyte and wide hash API Move the hash declarations into hash.h and the stdint.h discrimination into common.h. cstring.h includes both. Add mbs, wcs, mbuf, and wbuf entry points, including the case forms, for djb2 and FNV-1a. Each code unit contributes its low 8 bits, so ASCII text has one hash in both encodings. C++ shims overload on char const*, wchar_t const*, and both buffer forms. * docs(cstring): document hash groups, contract, and consumers - Document the hash contract, the djb2 and FNV-1a steps, and the published vectors in hash.h, README.md, and doc/mainpage.md; - Define group__cstring_api__hashing__djb2 and group__cstring_api__hashing__fnv1a, and keep the published references on those groups; - Place the C++ hash shims in namespace cstring, and import them in test.unit.hash.cxx; - Name the projects that call or link cstring, including the sistools programs; - Parenthesise the hash basis macros and note the FNV-1a prime in decimal; - Record the header-split and auto-buffer provenance items in TODO.md; - Spell NUL in the hash and status-code comments; * Hash (SDBM) (#46) * chore(misc): language discrimination; CI branch(es) * chore(misc): ensuring `\file` designators are correct * chore(mist): removed unused `CSTRING_VECTOR_ASSERT()` * chore(misc): tidying * fix(cstring): discriminate cstring_hash_t and use it for hashing Gate <stdint.h> on C99, on C++ with that header or C++11, and on MSVC 7.1 or later, and typedef cstring_hash_t to uint64_t when it is included; use cstring_hash_t for the djb2 and FNV-1a API and for the hash unit tests. * chore(performance tests): prefer `uint64_t` to `std::uint64_t` * test(cstring): exercise the hashing API from a C unit test Move the eight C hashing functions into **test.unit.hash** (`entry.c`) so the suite builds when `NO_CSTRING_CPP_API` is on. Keep the C++ shim checks in **test.unit.hash.cxx**, which stays behind that option. * test(cstring): lock high-byte and embedded-NUL hash results Cover octet 0xFF and an interior NUL in test.unit.hash. Exact narrow vectors stay off the wide build so a later wchar_t fix is not frozen. * feat(cstring): publish the djb2 and FNV-1a basis macros Add CSTRING_HASH_DJB2_SEED, CSTRING_HASH_FNV1A_OFFSET, and CSTRING_HASH_FNV1A_PRIME beside cstring_hash_t, and use them as the only copies of those constants in the hash implementation. Cite Yigit's djb2 page and the Fowler, Noll, and Vo notes and RFC 9923, and lock each macro to its historic literal in the C unit test. * fix * refactor(cstring): rename the case-folded hash suffix to _case Rename the case-folded hash functions from the `_ci` suffix to `_case` (`cstring_hash_djb2_case`, `cstring_hash_djb2_len_case`, and the FNV-1a twins), including the C++ shims and the unit tests. * refactor(cstring): rename the counted hash suffix from _len to _buf Rename the pointer-and-length hash functions from `_len` to `_buf` (`cstring_hash_djb2_buf`, `cstring_hash_djb2_buf_case`, and the FNV-1a twins), including the C++ shims and the unit tests. * refactor(cstring): .DEF file renames of hash functions * feat(cstring): add always-available multibyte and wide hash API Move the hash declarations into hash.h and the stdint.h discrimination into common.h. cstring.h includes both. Add mbs, wcs, mbuf, and wbuf entry points, including the case forms, for djb2 and FNV-1a. Each code unit contributes its low 8 bits, so ASCII text has one hash in both encodings. C++ shims overload on char const*, wchar_t const*, and both buffer forms. * docs(cstring): document hash groups, contract, and consumers - Document the hash contract, the djb2 and FNV-1a steps, and the published vectors in hash.h, README.md, and doc/mainpage.md; - Define group__cstring_api__hashing__djb2 and group__cstring_api__hashing__fnv1a, and keep the published references on those groups; - Place the C++ hash shims in namespace cstring, and import them in test.unit.hash.cxx; - Name the projects that call or link cstring, including the sistools programs; - Parenthesise the hash basis macros and note the FNV-1a prime in decimal; - Record the header-split and auto-buffer provenance items in TODO.md; - Spell NUL in the hash and status-code comments; * test(cstring): time djb2 and FNV-1a against a lose-lose baseline Add test.performance.hash. Each group is one scenario and length, with lose-lose, djb2, and FNV-1a, across cstring_t, multibyte and wide strings, and counted buffers, including case folding, embedded NULs, and high octets. A further algorithm is one extra row on each table. Stop cstring_strlcpy_safe_() from scanning past lim. createLen, assign, insert, and append pass a slice of cch characters that need not contain a NUL. Cover that with a 3-character createLen case in test.unit.cstring. * test(cstring): report hash spread from a scratch program Add test.scratch.hash. It builds 10000 distinct pseudo-random strings and prints full-hash uniqueness, per-octet occupancy, and residues for power-of-two hashtable sizes and a prime just below each, for lose-lose, djb2, and FNV-1a. * feat(cstring): add a 64-bit SDBM hash Add `cstring_hash_sdbm()` and the multibyte, wide, counted-buffer, and `_case` forms. The step is `hash * 65599 + octet` from seed 0, in a 64-bit accumulator. C++ shims are `hash_sdbm()` and `hash_sdbm_case()`. Unit tests lock the published vectors; the performance and scratch programs append an SDBM row after djb2 and FNV-1a. * fix * fix(cstring): hash every little-endian octet of a wide code unit Step once per octet of each wchar_t, low byte first, in the wide djb2, FNV-1a, and SDBM functions and in the lose-lose timing baseline. A multibyte string and its wide equivalent now differ; the published multibyte vectors stay as they are, and 64-bit djb2 is documented to match 32-bit djb2 only below 2^32. * fix: corrected old-VC++ compatibility * fix(cstring): recognise stdint.h on Visual C++ from VS 2010 Require _MSC_VER 1600 before treating Visual C++ as having <stdint.h>. Older MSVC keeps the unsigned __int64 fallback for cstring_hash_t. * test(cstring): assert _case hashes ignore setlocale Compare C-locale and tr_TR.UTF-8 _case results for octet 0xDD and U+00C0. Those inputs change under tolower and towlower, so the case fails until the fold stops following the process locale. * fix(cstring): fold _case hashes on ASCII A-Z only Map A-Z to a-z by adding 0x20 and leave every other code unit unchanged, in the djb2, FNV-1a, and SDBM functions and in the lose-lose baseline. setlocale no longer changes a _case result. * feat(cstring): specialise std::hash<cstring_t> on FNV-1a Provide std::hash<cstring_t> from C++11 as the FNV-1a hash of ptr and len, converted to size_t. djb2 and SDBM stay available as explicit functions. * test(cstring): rename hash tests off the old _ci and _len suffixes Rename the case and counted-buffer unit-test functions to _case and _buf, matching the hash API. Local bases in the case test follow the same suffix. * feat(cstring): add cstring_equal and cstring_compare Add cstring_equal(), returning cstring_truthy_t, and cstring_compare(), returning cstring_sint_t. Equal returns before reading when the lengths differ, and memcmp's the payloads only when they match. Compare returns a signed order of len code units. The multibyte build memcmp's the common prefix. C++ ==, !=, and < call those functions. Drop the cstring_vector example's private comparator. Its qsort adapters call cstring_compare(). * test(cstring): tolerate a host with no Turkish ctype locale Select tr_TR.UTF-8 or another Turkish ctype name when the host has one. A missing Turkish locale is not a failure. Linux CI generates tr_TR.UTF-8 so that comparison still runs there. * feat(cstring): build and test `cstring_char_t` as `wchar_t` (#43) * refactor(cstring): keep the character helper under test/ Move cstring.helpers.h from include/cstring/ to test/. Examples include it by relative path, and the test suites include it from cstring_testing.h. It is not part of the installed library contract. * fix * fix(cstring): check the wctomb() shift-state result GCC rejects a discarded wctomb() result, including a (void) cast. Test the NULL call that restores the initial shift state. * ci(cstring): run wide-string Windows cells without performance tests Add CI cells windows-cl-wide and windows-mingw-wide. They call the shared cell with --wide-strings, so unit tests, component tests, scratch tests, and examples run. Performance tests are not run. * fix(cstring): convert wide output without deprecated wctomb MSVC rejects wctomb as a deprecated CRT call, and a null destination is not a valid reset. Wide writes now keep a local mbstate_t and call wcrtomb_s there, and wcrtomb elsewhere. * docs(cstring): record the 4.1.0-alpha1 release notes Label the hash, comparison, and wide-string work as 4.1.0-alpha1 dated 6th October 2026. Drop the duplicate hash-shim and unit-test bullets, and move the --wide-strings note out of the released 4.0.19 section. Point NEWS and the Doxyfile project number at the same tag. --------- Co-authored-by: Matt Wilson <152443343+mwsis@users.noreply.github.com>
1 parent dd05c78 commit 810307c

64 files changed

Lines changed: 7669 additions & 718 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci-cell.yml‎

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ on:
2323
required: false
2424
type: string
2525
default: ''
26+
wide-strings:
27+
required: false
28+
type: boolean
29+
default: false
2630

2731
jobs:
2832
build:
@@ -76,6 +80,9 @@ jobs:
7680
export CC="${{ inputs.c-compiler }}"
7781
export CXX="${{ inputs.cpp-compiler }}"
7882
fi
83+
if [ "${{ inputs.wide-strings }}" = "true" ]; then
84+
PREPARE_ARGS+=(--wide-strings)
85+
fi
7986
./prepare_cmake.sh ${PREPARE_ARGS[@]+"${PREPARE_ARGS[@]}"}
8087
8188
- name: Build via build_cmake.sh
@@ -89,6 +96,18 @@ jobs:
8996
fi
9097
./build_cmake.sh
9198
99+
# Ubuntu runners do not ship tr_TR.UTF-8. The hash locale test
100+
# compares C with a Turkish ctype locale when one is installed.
101+
- name: Install Turkish locale (Linux)
102+
if: runner.os == 'Linux'
103+
shell: bash
104+
run: |
105+
set -euo pipefail
106+
sudo apt-get update
107+
sudo apt-get install -y locales
108+
sudo sed -i '/^# tr_TR.UTF-8 UTF-8/s/^# //' /etc/locale.gen
109+
sudo locale-gen
110+
92111
- name: CTest via ctest_cmake.sh
93112
shell: bash
94113
run: |
@@ -147,6 +166,18 @@ jobs:
147166
\( -name 'example*' ! -name '*.md' \) \
148167
\) -exec chmod +x {} +
149168
169+
# Ubuntu runners do not ship tr_TR.UTF-8. The hash locale test
170+
# compares C with a Turkish ctype locale when one is installed.
171+
- name: Install Turkish locale (Linux)
172+
if: runner.os == 'Linux'
173+
shell: bash
174+
run: |
175+
set -euo pipefail
176+
sudo apt-get update
177+
sudo apt-get install -y locales
178+
sudo sed -i '/^# tr_TR.UTF-8 UTF-8/s/^# //' /etc/locale.gen
179+
sudo locale-gen
180+
150181
- name: Automated tests (unit then component) [non-Windows]
151182
if: runner.os != 'Windows'
152183
shell: bash
@@ -163,8 +194,9 @@ jobs:
163194
if /I "${{ inputs.toolchain }}"=="mingw" set "PATH=${{ steps.msys2.outputs.msys2-location }}\mingw64\bin;%PATH%"
164195
call run_all_automated_tests.cmd
165196
197+
# Wide-string cells do not run performance tests.
166198
- name: Performance tests [non-Windows]
167-
if: runner.os != 'Windows'
199+
if: runner.os != 'Windows' && inputs.wide-strings == false
168200
shell: bash
169201
env:
170202
# Mild CI smoke budget (Release default is 100000 / 2).
@@ -177,7 +209,7 @@ jobs:
177209
./run_all_performance_tests.sh -M
178210
179211
- name: Performance tests [Windows]
180-
if: runner.os == 'Windows'
212+
if: runner.os == 'Windows' && inputs.wide-strings == false
181213
shell: cmd
182214
env:
183215
# windows-cl keeps Release defaults; MinGW alone is throttled

‎.github/workflows/ci.yml‎

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ on:
1010
- rc1
1111
- rc2
1212
- rc3
13-
- directories
13+
- hash-review
1414
pull_request:
1515

1616
concurrency:
@@ -64,6 +64,34 @@ jobs:
6464
toolchain: ${{ matrix.toolchain }}
6565
secrets: inherit
6666

67+
# Same Windows cells as above, configured with --wide-strings. Performance
68+
# steps are skipped in the cell when that input is set.
69+
cell-wide:
70+
name: CI (${{ matrix.id }})
71+
strategy:
72+
fail-fast: false
73+
matrix:
74+
include:
75+
- id: windows-cl-wide
76+
os: windows-latest
77+
c_compiler: cl
78+
cpp_compiler: cl
79+
- id: windows-mingw-wide
80+
os: windows-latest
81+
c_compiler: gcc
82+
cpp_compiler: g++
83+
toolchain: mingw
84+
uses: ./.github/workflows/ci-cell.yml
85+
with:
86+
build-type: Release
87+
c-compiler: ${{ matrix.c_compiler }}
88+
cell-id: ${{ matrix.id }}
89+
cpp-compiler: ${{ matrix.cpp_compiler }}
90+
os: ${{ matrix.os }}
91+
toolchain: ${{ matrix.toolchain }}
92+
wide-strings: true
93+
secrets: inherit
94+
6795
install-smoke:
6896
name: Install smoke (${{ matrix.os }}, ${{ matrix.toolchain || matrix.c_compiler }})
6997
runs-on: ${{ matrix.os }}
@@ -264,3 +292,49 @@ jobs:
264292
run: |
265293
set -euo pipefail
266294
SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/_build" ./run_all_automated_tests.sh -M
295+
296+
wide-strings:
297+
name: Build with --wide-strings
298+
runs-on: ubuntu-latest
299+
300+
steps:
301+
- uses: actions/checkout@v4
302+
303+
- name: Install test dependencies (STLSoft + shwild + xTests)
304+
uses: ./.github/actions/install-sis-deps
305+
with:
306+
build-type: ${{ env.BUILD_TYPE }}
307+
c-compiler: gcc
308+
cpp-compiler: g++
309+
dependencies: STLSoft shwild xTests
310+
311+
- name: Configure via prepare_cmake.sh --wide-strings
312+
shell: bash
313+
run: |
314+
set -euo pipefail
315+
# Same prefix rule as the --no-cpp job: xTests::core links
316+
# STLSoft::STLSoft, which exists only when that package is
317+
# imported. --wide-strings defines CSTRING_USE_WIDE_STRINGS.
318+
export CMAKE_PREFIX_PATH="${{ env.SIS_DEPS }}"
319+
./prepare_cmake.sh --wide-strings
320+
321+
- name: Build via build_cmake.sh
322+
shell: bash
323+
run: |
324+
set -euo pipefail
325+
export CMAKE_PREFIX_PATH="${{ env.SIS_DEPS }}"
326+
./build_cmake.sh
327+
328+
- name: Run automated tests
329+
shell: bash
330+
run: |
331+
set -euo pipefail
332+
SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/_build" ./run_all_automated_tests.sh -M
333+
334+
- name: Run examples
335+
shell: bash
336+
run: |
337+
set -euo pipefail
338+
SIS_EXAMPLE_SMOKE=1 \
339+
SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/_build" \
340+
./run_all_examples.sh -M

‎CHANGES.md‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,34 @@
11
# cstring - Changes <!-- omit in toc -->
22

33

4+
## 4.1.0-alpha1 - 6th October 2026
5+
6+
* Added 64-bit `djb2` hash functions: `cstring_hash_djb2()`, `cstring_hash_djb2_case()`, `cstring_hash_djb2_buf()`, and `cstring_hash_djb2_buf_case()`;
7+
* Added 64-bit `FNV-1a` hash functions: `cstring_hash_fnv1a()`, `cstring_hash_fnv1a_case()`, `cstring_hash_fnv1a_buf()`, and `cstring_hash_fnv1a_buf_case()`;
8+
* Added multibyte and wide hash entry points `cstring_hash_djb2_mbs()`, `cstring_hash_djb2_wcs()`, `cstring_hash_djb2_mbuf()`, `cstring_hash_djb2_wbuf()`, and the `_case` forms, and the same set for `cstring_hash_fnv1a()`;
9+
* Added 64-bit `SDBM` hash functions: `cstring_hash_sdbm()`, `cstring_hash_sdbm_case()`, and the multibyte, wide, and counted-buffer forms, including `_case`;
10+
* Added `cstring_hash_t` typedef (`uint64_t`);
11+
* Added **common.h** for shared `<stdint.h>` discrimination, and **hash.h** for the hash API, included from **cstring.h**;
12+
* Recognised `<stdint.h>` on Visual C++ from `_MSC_VER` 1600 (Visual Studio 2010); older MSVC uses `unsigned __int64` for `cstring_hash_t`;
13+
* Added C++ hash access shims `hash_djb2()`, `hash_djb2_case()`, `hash_fnv1a()`, `hash_fnv1a_case()`, `hash_sdbm()`, and `hash_sdbm_case()` for `cstring_t` (reference and pointer), `char const*`, `wchar_t const*`, and both buffer forms;
14+
* Added unit test **test.unit.hash** (C hashing API, built with and without the C++ API) and **test.unit.hash.cxx** (C++ hash shims);
15+
* Added **test.performance.hash**, timing `djb2`, `FNV-1a`, and `SDBM` against a degenerate lose-lose sum for cstring, multibyte, wide, counted-buffer, and case-insensitive inputs;
16+
* Suppressed MSVC warning 4996 (`getenv` in **perf_harness.hpp**) on **test.performance.hash**, matching the other performance programs;
17+
* Added scratch program **test.scratch.hash**, reporting full-hash, per-octet, and hashtable-modulo spread of lose-lose, `djb2`, `FNV-1a`, and `SDBM` on 10000 pseudo-random strings;
18+
* Stopped counted copies (`cstring_createLen()`, `cstring_assignLen()`, and the insert and append forms) from reading past `cch` when that slice contains no NUL;
19+
* Published `CSTRING_HASH_DJB2_SEED`, `CSTRING_HASH_FNV1A_OFFSET`, `CSTRING_HASH_FNV1A_PRIME`, `CSTRING_HASH_SDBM_MULTIPLIER`, and `CSTRING_HASH_SDBM_SEED` as the djb2, FNV-1a, and SDBM basis constants;
20+
* Documented `CSTRING_HASH_DJB2_SEED`, `CSTRING_HASH_FNV1A_OFFSET`, `CSTRING_HASH_FNV1A_PRIME`, `CSTRING_HASH_SDBM_MULTIPLIER`, and `CSTRING_HASH_SDBM_SEED` with references to the published djb2, FNV-1a, and SDBM algorithms;
21+
* Hashed every octet of each wide code unit, low byte first, so a multibyte string and its wide equivalent differ;
22+
* Counted those same wide octets in the lose-lose timing baseline;
23+
* Documented the hash contract (forms, little-endian wide code units, case fold, published multibyte vectors, and the 32-bit djb2 limit) and grouped the algorithms as `group__cstring_api__hashing__djb2`, `group__cstring_api__hashing__fnv1a`, and `group__cstring_api__hashing__sdbm`;
24+
* Folded `_case` on ASCII A-Z only, independent of the process locale, and applied that same fold in the lose-lose timing baseline;
25+
* Specialised `std::hash<cstring_t>` on FNV-1a for C++11 and later;
26+
* Added `cstring_equal()` (`cstring_truthy_t`) and `cstring_compare()` (`cstring_sint_t`). Equality is the truthy function; compare is the signed order. C++ `==`, `!=`, and `<` call them;
27+
* Added unit tests **test.unit.compare** and **test.unit.compare.cxx**;
28+
* Wide-string build via **prepare_cmake.sh** `--wide-strings` (`CSTRING_USE_WIDE_STRINGS` on **cstring::core**): `cstring_char_t` is `wchar_t`; `cstring_write()` / `cstring_writeline()` emit the payload as multibyte bytes; examples and tests take literals and comparisons from **cstring.helpers.h** (`CSTRING_T_()`, `CSTRING_STRCMP_()`, `CSTRING_STRNCMP_()`), which is not part of the library contract; performance tests are not built; CI job **wide-strings**;
29+
* Wide-string CI cells **windows-cl-wide** and **windows-mingw-wide** run the Windows cell suite with `--wide-strings`, and do not run performance tests;
30+
31+
432
## 4.0.19 - 4th October 2026
533

634
* Corrected `cstring_readline()` so `numRead` counts every character read from the stream for the line, including the terminator; CR and LF are not stored;

‎CMakeLists.txt‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# Purpose: Top-level CMake lists file for cstring
66
#
77
# Created: 21st December 2023
8-
# Updated: 3rd October 2026
8+
# Updated: 4th October 2026
99
#
1010
# ######################################################################## #
1111

@@ -117,6 +117,8 @@ option(BUILD_EXAMPLES "Build examples" ON)
117117

118118
option(BUILD_TESTING "Build tests" ON)
119119

120+
option(CSTRING_USE_WIDE_STRINGS "Compile cstring_char_t as wchar_t" OFF)
121+
120122
option(NO_CSTRING_CPP_API "Disable C++ API (and associated C++ examples/tests)" OFF)
121123

122124
option(NO_P99 "Do not recognise p99 for performance tests" OFF)

‎Doxyfile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
PROJECT_NAME = "cstring"
44
PROJECT_BRIEF = "Extensible C-style strings and vectors of such, for Unix and Windows"
5-
PROJECT_NUMBER = 4.0.19
5+
PROJECT_NUMBER = 4.1.0-alpha1
66

77
# Prefer SIS_CMAKE_BUILD_DIR-aligned output (same convention as Diagnosticism).
88
# ./dox/ remains gitignored for legacy/local runs that override OUTPUT_DIRECTORY.

‎FAQ.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ See [INSTALL.md](./INSTALL.md) for details of how to install **cstring**.
4646

4747
## Q3: "How do I use cstring?"
4848

49-
Include **cstring/cstring.h** (and **cstring/cstring.vector.h** where needed)
49+
Include **cstring/cstring.h** (which includes **cstring/hash.h**; and
50+
**cstring/cstring.vector.h** where needed)
5051
and link against **libcstring** (the **CMake** target is `cstring::core`).
5152
Create and destroy instances with `cstring_create()` / `cstring_destroy()`,
5253
and mutate with `cstring_assign()`, `cstring_append()`, and related APIs.
@@ -61,8 +62,8 @@ A minimal sketch:
6162

6263
int main(void)
6364
{
64-
cstring_t cs;
65-
CSTRING_RC rc = cstring_create(&cs, "Hello");
65+
cstring_t cs;
66+
CSTRING_RC rc = cstring_create(&cs, "Hello");
6667

6768
if (CSTRING_RC_SUCCESS != rc)
6869
{

‎INSTALL.md‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
**cstring** is a classic-form C library, insofar as it has implementation
44
files in its **src** directory and header files in its **include/cstring**
55
directory. Thus, once "installed", one must simply include
6-
**cstring/cstring.h** (and, where needed, **cstring/cstring.vector.h**), and
7-
compile-in or link-in the implementation.
6+
**cstring/cstring.h** (which includes **cstring/hash.h**) and, where needed,
7+
**cstring/cstring.vector.h**, and compile-in or link-in the implementation.
88

99
The **C** API has no non-standard dependencies. Building the project's tests
1010
additionally requires **STLSoft** and **xTests** (and optionally recognises
@@ -56,6 +56,8 @@ The primary choice for installation is by use of **CMake**.
5656
(`NO_CSTRING_CPP_API`);
5757
* `--no-p99` — do not recognise **p99** (`NO_P99`);
5858
* `--no-shwild` — do not recognise **shwild** (`NO_SHWILD`);
59+
* `--wide-strings` — compile `cstring_char_t` as `wchar_t`
60+
(`CSTRING_USE_WIDE_STRINGS`); performance tests are not built;
5961
* `--disable-examples` / `-E` — omit examples (`BUILD_EXAMPLES=OFF`);
6062
* `--disable-testing` / `-T` — omit tests (`BUILD_TESTING=OFF`);
6163
* `--stlsoft-root-dir` / `-s` — pass an **STLSoft** source-tree root when
@@ -106,8 +108,8 @@ The primary choice for installation is by use of **CMake**.
106108

107109
int main(void)
108110
{
109-
cstring_t cs;
110-
CSTRING_RC rc = cstring_create(&cs, "Hello");
111+
cstring_t cs;
112+
CSTRING_RC rc = cstring_create(&cs, "Hello");
111113

112114
if (CSTRING_RC_SUCCESS != rc)
113115
{
@@ -164,10 +166,11 @@ The primary choice for installation is by use of **CMake**.
164166
In that case:
165167

166168
* add **cstring**'s **include** directory to your project's include path;
167-
* compile **src/cstring.core.c** and **src/cstring.vector.c** into your build
168-
(or link a previously built **libcstring**); and
169+
* compile **src/cstring.core.c**, **src/cstring.hash.c**, and
170+
**src/cstring.vector.c** into your build (or link a previously built
171+
**libcstring**); and
169172
* `#include <cstring/cstring.h>` (and **cstring/cstring.vector.h** where
170-
needed).
173+
needed). **cstring.h** includes **cstring/hash.h**.
171174

172175

173176
<!-- ########################### end of file ########################### -->

‎NEWS.md‎

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,36 @@
11
# cstring - News <!-- omit in toc -->
22

33

4-
| Date | News Item | Details |
5-
| ------------------- | -------------------------------- | ------- |
4+
| Date | News Item | Details |
5+
| ------------------- | ------------------------------------ | ------- |
66
| Available from [**cstring** project on GitHub](https://synesissoftware.com/cstring): |
7+
| 6th October 2026 | Release of [cstring 4.1.0-alpha1](https://github.com/synesissoftware/cstring/releases/tag/4.1.0-alpha1) | `djb2`, `FNV-1a`, and `SDBM`; `cstring_equal()` / `cstring_compare()`; `--wide-strings`; counted copies stop at `cch` |
78
| 4th October 2026 | Release of [cstring 4.0.19](https://github.com/synesissoftware/cstring/releases/tag/4.0.19) | lone CR ends a line; `numRead` includes EOL; readonly truncate; zero-size realloc frees; **test.performance.cstring_readline**; shortened test and example directories; **test.unit.cstring**; README usage modes |
89
| 29th September 2026 | Release of [cstring 4.0.18](https://github.com/synesissoftware/cstring/releases/tag/4.0.18) | `_WIN32` arena gate; **win.c**; static `CoTaskMem*` (**ole32**) |
910
| 29th September 2026 | Release of [cstring 4.0.17](https://github.com/synesissoftware/cstring/releases/tag/4.0.17) | Perf tests; `insertAt` fix; component I/O; Windows arena rename |
1011
| 27th September 2026 | Release of [cstring 4.0.16](https://github.com/synesissoftware/cstring/releases/tag/4.0.16) | Phase 4b helpers, native `.cmd`, CI dogfood |
1112
| 6th September 2026 | Release of [cstring 4.0.15](https://github.com/synesissoftware/cstring/releases/tag/4.0.15) | Doxygen `\param`/`\retval` 76-rule |
1213
| 9th August 2026 | Release of [cstring 4.0.14](https://github.com/synesissoftware/cstring/releases/tag/4.0.14) | self-assignment safety |
13-
| 7th August 2026 | Release of [cstring 4.0.14-alpha1](https://github.com/synesissoftware/cstring/releases/tag/4.0.14-alpha1) | |
14+
| 7th August 2026 | Release of [cstring 4.0.14-alpha1](https://github.com/synesissoftware/cstring/releases/tag/4.0.14-alpha1) | Boilerplate alignment with b64 / Pantheios |
1415
| 2nd August 2026 | Release of [cstring 4.0.13](https://github.com/synesissoftware/cstring/releases/tag/4.0.13) | modular CI, examples, Doxygen, documentation |
1516
| 2nd September 2025 | Release of [cstring 4.0.12](https://github.com/synesissoftware/cstring/releases/tag/4.0.12) | GitHub Actions, CTest, MinGW make, **libver** |
16-
| 23rd February 2025 | Release of cstring 4.0.11 | boilerplate, simpler dependencies |
17-
| 25th October 2024 | Release of cstring 4.0.10 | CMake improvements, MinGW compatibility |
18-
| 28th January 2024 | Release of cstring 4.0.9 | CMake C11/C++14, `CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY` |
19-
| 12th January 2024 | Release of cstring 4.0.8 | `cstring.vector`, `cstring_write()`, wide strings, CMake |
17+
| 23rd February 2025 | Release of cstring 4.0.11 | boilerplate, simpler dependencies |
18+
| 25th October 2024 | Release of cstring 4.0.10 | CMake improvements, MinGW compatibility |
19+
| 28th January 2024 | Release of cstring 4.0.9 | CMake C11/C++14, `CSTRING_F_USE_WINDOWS_PROCESSHEAP_MEMORY` |
20+
| 12th January 2024 | Release of cstring 4.0.8 | `cstring.vector`, `cstring_write()`, wide strings, CMake |
2021
| Available from **http://synesis.com.au/software/cstring**: |
21-
| 20th February 2012 | Release of 3.6.2 | |
22-
| 24th January 2012 | Release of 3.6.1 | |
23-
| 22nd January 2012 | Release of 3.5.4 | |
24-
| 11th January 2010 | Release of 3.5.3 | |
25-
| 11th July 2009 | Release of 3.5.2 | |
26-
| 10th July 2009 | Release of 3.5.1 | |
27-
| 4th May 2008 | Release of 3.4.4 | |
28-
| 26th January 2007 | Release of 3.4.3 | |
29-
| 30th April 2006 | Release of 3.4.2 | |
30-
| 7th October 2005 | Release of 3.4.1 | |
31-
| 8th August 2005 | Release of 3.3.1 | |
32-
| 29th July 2005 | Release of 3.2.1 | |
22+
| 20th February 2012 | Release of 3.6.2 | Added CSTRING_RC_SYSTEMSPECIFICFAILURE, makefile fixes |
23+
| 24th January 2012 | Release of 3.6.1 | cstring.core renaming, heap growth flag, Win64 |
24+
| 22nd January 2012 | Release of 3.5.4 | Win64 and VC++ 10 compatibility |
25+
| 11th January 2010 | Release of 3.5.3 | Trivial formatting changes |
26+
| 11th July 2009 | Release of 3.5.2 | Implicit link fixes, Win64 compatibility |
27+
| 10th July 2009 | Release of 3.5.1 | I/O and substring APIs, compiler compatibility |
28+
| 4th May 2008 | Release of 3.4.4 | Additional error codes, Win64 and Safe String compatibility |
29+
| 26th January 2007 | Release of 3.4.3 | Distribution roots under versioned directory |
30+
| 30th April 2006 | Release of 3.4.2 | Minor edits and fixes |
31+
| 7th October 2005 | Release of 3.4.1 | cstring_flags_t, cstring_yield2(), versioned library names |
32+
| 8th August 2005 | Release of 3.3.1 | Memory allocation algorithm, dynamic OLE32 loading |
33+
| 29th July 2005 | Release of 3.2.1 | Initial public release |
3334

3435

3536
<!-- ########################### end of file ########################### -->

0 commit comments

Comments
 (0)