Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
- rc1
- rc2
- rc3
- perf-tests-win
- directories
pull_request:

concurrency:
Expand Down
21 changes: 21 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
# cstring - Changes <!-- omit in toc -->


## 4.0.19 - 4th October 2026

* Corrected `cstring_readline()` so `numRead` counts every character read from the stream for the line, including the terminator; CR and LF are not stored;
* Treated a lone CR (not followed by LF, including CR at end of stream) as a line terminator that returns `CSTRING_RC_SUCCESS` and pushes the following character back;
* Freed a zero-size realloc-arena block on every platform (`realloc(pv, 0)` allocates on some), leaving a `NULL` pointer unchanged;
* `cstring_readline()` returns the result of its opening `cstring_truncate()`, so a readonly destination yields `CSTRING_RC_READONLY` for an empty line or immediate end of file, and the payload and stream position are left unchanged;
* Suppressed the GCC 15 `-Wfree-nonheap-object` false positive on inlined `stlsoft::auto_buffer` destruction in **test.performance.cstring**;
* Documented memory contracts, allocators, and a default-use sketch in **README.md**, including `cstring_getStatusCodeStringLength()`, `CSTRING_VER`, and the default and index macros;
* Added **test.performance.cstring_readline** (filesystem timings gated on **p99**);
* Added **test.component.cstring_readline** case `TEST_cstring_readline_READONLY_RETAINS_PAYLOAD`;
* Added single-line LF, CRLF, and CR cases to **test.component.cstring_readline**; **test.component.cstring_vector_readLines** splits an embedded CR into its own line;
* Added borrowed-fixed construct and assign scenarios to **test.performance.cstring**, using `stlsoft::auto_buffer` for the borrowed anchor;
* Split regular unit-test into **test.unit.cstring** (C) and **unit.cstring.cxx** (C++);
* Shared component file fixtures in **test/component/component_fixture.hpp**, and shared `time_iterations`, `emit_row`, and `write_lines_file` in **test/performance/perf_harness.hpp**;
* Named unit cases `TEST_` in shouting snake case, keeping each API or type in its real spelling, and switched assertions to the terse xTests API;
* Passed `temp_file::CloseOnOpen` in **test.component.cstring_readline** so the creating handle is closed before `fopen`, which Windows otherwise rejects as a sharing violation;
* Removed empty unit cases and unused temporary-file names;
* Shortened unit-test directories under **test/unit/** to the subject: **auto-buffer** (**test.unit.auto-buffer**, formerly **test.unit.cstring.auto_buffer**), **cstring.cxx** (**test.unit.cstring.cxx**, formerly **test.unit.cstring.1**), **insert-replace** (**test.unit.insert-replace**, formerly **test.unit.cstring.2**), **status-codes** (**test.unit.status-codes**, formerly **test.unit.cstring_getStatusCodeString**), and **cstring_vector** (**test.unit.cstring_vector**);
* Shortened example directories under **examples/** to the subject (**auto-buffer**, **cstring**, **cstring_create**, **cstring_vector**, **cstring.dynload**, **cstring.global_memory**), with CMake targets **example.c.cstring.auto_buffer**, **example.c.cstring**, **example.c.cstring_create**, **example.c.cstring_vector**, **example.cpp.cstring.dynload**, and **example.cpp.cstring.global_memory** (formerly **example.cpp.HGLOBAL_on_x64**);


## 4.0.18 - 29th September 2026

* Gated Windows arena flags and WinAPI allocators on `_WIN32` in **cstring.h** and **cstring.core.c**, so 32- and 64-bit Windows builds expose them without a `WIN32` or `WIN64` define;
Expand Down
12 changes: 6 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Purpose: Top-level CMake lists file for cstring
#
# Created: 21st December 2023
# Updated: 29th September 2026
# Updated: 3rd October 2026
#
# ######################################################################## #

Expand Down Expand Up @@ -137,8 +137,8 @@ option(NO_SHWILD "Do not recognise shwild for xTests pattern-match assertions" O
#
# optional:
# - p99 - optional for performance tests: per-iteration percentiles
# throughout when found; also gates the filesystem file_lines vs
# cstring_vector_readLines suite (unless NO_P99);
# throughout when found; also gates the filesystem suites
# (file_lines, cstring_readline) unless NO_P99;
# - shwild - for enhanced match constructs in xTests (unless NO_SHWILD);
#
# note: NO_CSTRING_CPP_API skips C++ examples and remaining C++ tests; C
Expand Down Expand Up @@ -214,7 +214,7 @@ endif(BUILD_TESTING)

if(BUILD_TESTING)

set(xTests_REQUIRED_VERSION_ 0.26)
set(xTests_REQUIRED_VERSION_ 0.26.5)

find_package(xTests ${xTests_REQUIRED_VERSION_} REQUIRED)

Expand All @@ -236,11 +236,11 @@ if(BUILD_TESTING)
message("-- CMake package p99 found (version ${p99_VERSION})")
else()

message("-- CMake package p99 not found; performance percentiles and file_lines suite disabled")
message("-- CMake package p99 not found; performance percentiles and filesystem suites disabled")
endif()
else()

message("-- p99 recognition disabled (NO_P99); performance percentiles and file_lines suite disabled")
message("-- p99 recognition disabled (NO_P99); performance percentiles and filesystem suites disabled")
endif()
endif(BUILD_TESTING)

Expand Down
2 changes: 1 addition & 1 deletion Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

PROJECT_NAME = "cstring"
PROJECT_BRIEF = "Extensible C-style strings and vectors of such, for Unix and Windows"
PROJECT_NUMBER = 4.0.18
PROJECT_NUMBER = 4.0.19

# Prefer SIS_CMAKE_BUILD_DIR-aligned output (same convention as Diagnosticism).
# ./dox/ remains gitignored for legacy/local runs that override OUTPUT_DIRECTORY.
Expand Down
6 changes: 3 additions & 3 deletions FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ require **STLSoft** and **xTests** (and may optionally recognise **shwild**).
## Q5: "How do I build without the C++ examples and tests?"

Pass `--no-cpp` (or `-C`) to **prepare_cmake.sh**, which sets CMake
`NO_CSTRING_CPP_API=ON`. That omits C++ examples and remaining C++ tests.
`NO_CSTRING_CPP_API=ON`. That omits C++ examples and **test.unit.cstring.cxx**.

The **C** unit-tests still require **STLSoft** and **xTests** unless you also
pass `--disable-testing` / `-T`.
The **C** unit-tests, including **test.unit.cstring**, still require
**STLSoft** and **xTests** unless you also pass `--disable-testing` / `-T`.


## Q6: "Where are the examples?"
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
| Date | News Item | Details |
| ------------------- | -------------------------------- | ------- |
| Available from [**cstring** project on GitHub](https://synesissoftware.com/cstring): |
| 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 |
| 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**) |
| 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 |
| 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 |
Expand Down
107 changes: 95 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# cstring <!-- omit in toc -->

**C**-style **string**s is a small, standalone library, that provides extensible C-style string instances and extensible arrays of such, for Unix and Windows.
Small standalone C library that provides extensible C-style strings and extensible arrays of those strings, for Unix and Windows.


![C](https://img.shields.io/badge/C-00599C?style=flat&logo=c&logoColor=white)
![C++](https://img.shields.io/badge/C%2B%2B-00599C?style=flat&logo=c%2B%2B&logoColor=white)
[![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
[![GitHub release](https://img.shields.io/github/v/release/synesissoftware/cstring.svg)](https://github.com/synesissoftware/cstring/releases/latest)
[![Last Commit](https://img.shields.io/github/last-commit/synesissoftware/cstring)](https://github.com/synesissoftware/cstring/commits/master)
Expand All @@ -14,9 +13,14 @@
## Table of Contents <!-- omit in toc -->

- [Introduction](#introduction)
- [Usage modes](#usage-modes)
- [Default use](#default-use)
- [Storage contract](#storage-contract)
- [Allocators](#allocators)
- [Installation](#installation)
- [Components](#components)
- [Types](#types)
- [Constants](#constants)
- [String API](#string-api)
- [Status and capacity](#status-and-capacity)
- [Creation/destruction functions](#creationdestruction-functions)
Expand All @@ -34,9 +38,79 @@

## Introduction

**cstring** is a small, standalone library that provides extensible C-style string instances and extensible arrays of such, 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. Optional C++ examples and remaining C++ tests may be omitted with `--no-cpp` / `NO_CSTRING_CPP_API`. Building tests requires **STLSoft** and **xTests** (and may optionally recognise **shwild**).
The **C** API has no non-standard dependencies. Building tests requires **STLSoft** and **xTests** (and may optionally recognise **shwild**).


## Usage modes

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.


### Default use

```c
#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;
}
```


### Storage contract

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**](./examples/c/cstring/);
* Auto-buffer: [**example.c.cstring.auto_buffer**](./examples/c/auto-buffer/);
* Win32 global memory: [**example.cpp.cstring.global_memory**](./examples/cpp/cstring.global_memory/).


### Allocators

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()` stores `cstring_t_DEFAULT`. That instance does not need `cstring_destroy()`. Every `cstring_create*` does;
* `cstring_yield2()` hands back an owned payload. Borrowed and readonly instances refuse it. A Windows DLL built on `realloc` returns `CSTRING_RC_CANNOTYIELDFROMSO`;
* The character type is `char` unless `CSTRING_USE_WIDE_STRINGS` is set (normally both `UNICODE` and `_UNICODE` on Windows). `CSTRING_NO_USE_WIDE_STRINGS` forces `char`. That choice is made at compile time;
* Custom arenas (`CSTRING_F_USE_CUSTOMARENAFUNCTIONS`) are declared and return `CSTRING_RC_CUSTOMARENANOTSUPPORTED`. `CSTRING_F_MEMORY_IS_OFFSET` is set by the implementation and is not a client mode.


## Installation
Expand Down Expand Up @@ -76,6 +150,15 @@ The C API is based around two structures:
```


### Constants

* `CSTRING_VER` — the composite library version;
* `cstring_t_DEFAULT` — `{ 0, NULL, 0, 0 }`, an uninitialised `cstring_t`. `cstring_init()` assigns this;
* `cstring_vector_t_DEFAULT` — the same shape for a `cstring_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 for `cstring_insert()`, `cstring_insertLen()`, `cstring_replace()`, and `cstring_replaceLen()`;


### String API

Defined in **cstring/cstring.h**:
Expand All @@ -84,6 +167,7 @@ Defined in **cstring/cstring.h**:
#### Status and capacity

* `cstring_getStatusCodeString()` — returns a nul-terminated description of a `CSTRING_RC` code;
* `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;

Expand Down Expand Up @@ -134,16 +218,16 @@ Defined in **cstring/cstring.vector.h**:

## Examples

Examples live under **examples/** (`c/` and `cpp/`), each with a short **README.md**. Build them with `BUILD_EXAMPLES` (on by default); run via **run_all_examples.sh**.
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.auto_buffer**](./examples/c/example.c.auto_buffer/) | C | Borrowed buffer that may grow to the heap |
| [**example.c.cstring**](./examples/c/example.c.cstring/) | C | Core `cstring_t` create / assign / append / truncate / copy / swap |
| [**example.c.cstring_create**](./examples/c/example.c.cstring_create/) | C | Minimal `cstring_create()` |
| [**example.c.cstring_vector**](./examples/c/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**](./examples/cpp/example.cpp.cstring.dynload/) | C++ | Windows-only dynamic load of the cstring DLL |
| [**example.cpp.HGLOBAL_on_x64**](./examples/cpp/example.cpp.HGLOBAL_on_x64/) | C++ | Windows-only `CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY` |
| [**example.c.cstring**](./examples/c/cstring/) | C | Core `cstring_t` create / assign / append / truncate / copy / swap |
| [**example.c.cstring.auto_buffer**](./examples/c/auto-buffer/) | C | Borrowed buffer that may grow to the heap |
| [**example.c.cstring_create**](./examples/c/cstring_create/) | C | Minimal `cstring_create()` |
| [**example.c.cstring_vector**](./examples/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**](./examples/cpp/cstring.dynload/) | C++ | Windows-only dynamic load of the cstring DLL |
| [**example.cpp.cstring.global_memory**](./examples/cpp/cstring.global_memory/) | C++ | Windows-only `CSTRING_F_USE_WINDOWS_GLOBAL_MEMORY` |


## Project Information
Expand Down Expand Up @@ -197,4 +281,3 @@ Projects in which **cstring** is used include:


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

7 changes: 6 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@
* [x] ~~~Delete Visual Studio 98 files~~~ - ✅;
* [x] ~~~Delete Visual Studio 2003+ files~~~ - ✅;
* [x] ~~~discriminate on `_WIN32` in implementation (and maybe also in API)~~~ - ✅;
* [ ] check `CSTRING_USE_WINAPI_`;
* [x] ~~~check `CSTRING_USE_WINAPI_`~~~;
* [ ] custom arena(s);
* [ ] `cstring_vector_readlineEx()` that takes a flag to prevent truncate, thereby allowing client code to add to an existing string;
* [ ] when go to 5.x, change the name of `cstring_vector_readLines()` to `cstring_vector_readlines()`;
* [ ] when go to 5.x, consider use of SSO;
* [ ] when go to 5.x, consider expanding `cstring_vector_t` to allow it to own the memory of the strings it manages, such that can do a single file read and then break up into strings without allocating payload memory;


## Performance improvements
Expand All @@ -38,6 +42,7 @@
* [x] ~~~`CMAKE_INSTALL_LIBDIR` (replaces legacy `LIB_INSTALL_DIR`)~~~ - ✅;
* [x] ~~~**CTest**~~~ - ✅;
* [ ] build DLL on Windows;
* [ ] build dylib on macOS;
* [x] ~~~`/MT` build option for Visual C++ (`--msvc-mt` / `MSVC_USE_MT`)~~~ - ✅;
* [x] ~~~**shwild** dependency (testing only; `--no-shwild` / `NO_SHWILD`)~~~ - ✅;
* [x] ~~~Doxygen (**Doxyfile**, **doc/mainpage.md**, **generate_doxygen.sh**)~~~ - ✅;
Expand Down
8 changes: 4 additions & 4 deletions examples/c/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten
add_subdirectory(example.c.auto_buffer)
add_subdirectory(example.c.cstring)
add_subdirectory(example.c.cstring_create)
add_subdirectory(example.c.cstring_vector)
add_subdirectory(auto-buffer)
add_subdirectory(cstring)
add_subdirectory(cstring_create)
add_subdirectory(cstring_vector)
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten
define_example_program(example.cpp.HGLOBAL_on_x64 main.cpp)
define_example_program(example.c.cstring.auto_buffer main.c)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# example.c.auto_buffer <!-- omit in toc -->
# example.c.cstring.auto_buffer <!-- omit in toc -->


## Purpose
Expand All @@ -14,7 +14,7 @@ Built when `BUILD_EXAMPLES` is enabled (the default).
## Run

```sh
${SIS_CMAKE_BUILD_DIR:-./_build}/examples/c/example.c.auto_buffer/example.c.auto_buffer
${SIS_CMAKE_BUILD_DIR:-./_build}/examples/c/auto-buffer/example.c.cstring.auto_buffer
```


Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/* /////////////////////////////////////////////////////////////////////////
* File: examples/c/example.c.auto_buffer/main.c
* File: examples/c/auto-buffer/main.c
*
* Purpose: Example illustrating borrowed buffer growth to the heap
* (`CSTRING_F_MEMORY_IS_BORROWED` +
* `CSTRING_F_MEMORY_CAN_GROW_TO_HEAP`).
*
* Created: 28th July 2011
* Updated: 2nd August 2026
* Updated: 4th October 2026
*
* ////////////////////////////////////////////////////////////////////// */

Expand Down
Loading
Loading