# HG changeset patch # User Mike Becker # Date 1766927414 -3600 # Node ID 94360453bce42d9a07ffae58a6ccb96f7b20a302 # Parent cf19b7820ff06effe2e3da350465ee07f1882b4b partially revert the changes to cx_strcat() and add CX_NULLSTR macro relates to #792 diff -r cf19b7820ff0 -r 94360453bce4 CHANGELOG --- a/CHANGELOG Thu Dec 25 12:07:37 2025 +0100 +++ b/CHANGELOG Sun Dec 28 14:10:14 2025 +0100 @@ -11,6 +11,7 @@ * adds line continuation support to CxProperties / CxPropertiesConfig * adds cx_hash_key_as_string() * adds support for CxHashKey pointers in CX_HASH_KEY() and all map functions + * adds CX_NULLSTR convenience macro * adds cx_strat() * adds cx_bstr() and cx_bstr_m() * adds cxBufferMaximumCapacity() @@ -22,7 +23,6 @@ * changes cxBufferTerminate() so that position and size are equal after a successful operation * changes cxBufferPutString() to accept any kind of string that cx_strcast() supports * changes that multiple string.h functions now also automatically apply cx_strcast() where possible - * changes cx_strcat() family of function to return an int and removes two unnecessary variants * changes the names of cxDefineDestructor() and cxDefineAdvancedDestructor() to cxSetDestructor() and cxSetdvancedDestructor() * changes the name of cxCollectionCompareFunc() to cxSetCompareFunc() @@ -41,12 +41,13 @@ were not returning zero after freeing the memory when passed a size of zero * fixes that cx_list_default_insert_array() has the wrong nonnull attribute when used for cxListEmplaceArray() * removes the CX_STR() macro and instead makes the cx_str() inlinable + * removes the cx_str*_m() functions * removes the sort_members feature from CxJsonWriter * removes the source and sink API from properties.h * removes the flush feature from CxBuffer * removes the ability to remove elements from the iterators created with cxIterator() and cxIteratorPtr() * removes several unnecessary convenience functions - * removes the complicated wrapping for pointer lists + * removes the complicated wrapping of pointer lists Version 3.2 - 2025-11-30 ------------------------ diff -r cf19b7820ff0 -r 94360453bce4 docs/Writerside/topics/about.md --- a/docs/Writerside/topics/about.md Thu Dec 25 12:07:37 2025 +0100 +++ b/docs/Writerside/topics/about.md Sun Dec 28 14:10:14 2025 +0100 @@ -38,6 +38,7 @@ * adds line continuation support to CxProperties / CxPropertiesConfig * adds cx_hash_key_as_string() * adds support for CxHashKey pointers in CX_HASH_KEY() and all map functions +* adds CX_NULLSTR convenience macro * adds cx_strat() * adds cx_bstr() and cx_bstr_m() * adds cxBufferMaximumCapacity() @@ -49,7 +50,6 @@ * changes cxBufferTerminate() so that position and size are equal after a successful operation * changes cxBufferPutString() to accept any kind of string that cx_strcast() supports * changes that multiple string.h functions now also automatically apply cx_strcast() where possible -* changes cx_strcat() family of function to return an int and removes two unnecessary variants * changes the names of cxDefineDestructor() and cxDefineAdvancedDestructor() to cxSetDestructor() and cxSetdvancedDestructor() * changes the name of cxCollectionCompareFunc() to cxSetCompareFunc() @@ -68,6 +68,7 @@ were not returning zero after freeing the memory when passed a size of zero * fixes that cx_list_default_insert_array() has the wrong nonnull attribute when used for cxListEmplaceArray() * removes the CX_STR() macro and instead makes the cx_str() inlinable +* removes the cx_str*_m() functions * removes the sort_members feature from CxJsonWriter * removes the source and sink API from properties.h * removes the flush feature from CxBuffer diff -r cf19b7820ff0 -r 94360453bce4 docs/Writerside/topics/string.h.md --- a/docs/Writerside/topics/string.h.md Thu Dec 25 12:07:37 2025 +0100 +++ b/docs/Writerside/topics/string.h.md Sun Dec 28 14:10:14 2025 +0100 @@ -49,6 +49,7 @@ void cx_strfree_a(const CxAllocator *alloc, cxmutstr *str); +#define CX_NULLSTR cx_mutstr(NULL) #define CX_SFMT(s) (int) (s).length, (s).ptr #define CX_PRIstr ".*s" #define cx_strcast(s) // converts any string to cxstring @@ -119,23 +120,26 @@ ```C #include -int cx_strcat(cxmutstr *str, size_t count, ... ); +cxmutstr cx_strcat(cxmutstr str, size_t count, ... ); -int cx_strcat_a(const CxAllocator *allocator, - cxmutstr *str, size_t count, ... ); +cxmutstr cx_strcat_a(const CxAllocator *allocator, + cxmutstr str, size_t count, ... ); size_t cx_strlen(size_t count, ...); ``` The `cx_strcat_a()` function takes `count` UCX strings (`cxstring` or `cxmutstr` - not pointers!), -allocates memory in `str` for a concatenation of those strings _with a single allocation_, +reallocates the memory in `str` for a concatenation of those strings _with a single allocation_, and appends the contents of the strings to `str`. `cx_strcat()` is equivalent, except that it uses the [default allocator](allocator.h.md#default-allocator). +When there is no `str` where the other strings shall be appended to, you can pass `CX_NULLSTR` as first argument. +In that case, a completely new string is allocated. + Example usage: ```C -cxmutstr str = {0}; -cx_strcat(&str, 2, cx_str("Hello, "), cx_str("World!")); +cxmutstr str = cx_strcat(CX_NULLSTR, 2, + cx_str("Hello, "), cx_str("World!")); ``` The function `cx_strlen()` sums the length of the specified strings. diff -r cf19b7820ff0 -r 94360453bce4 src/cx/string.h --- a/src/cx/string.h Thu Dec 25 12:07:37 2025 +0100 +++ b/src/cx/string.h Sun Dec 28 14:10:14 2025 +0100 @@ -41,6 +41,9 @@ #include +/** Convenience macro for creating a null string */ +#define CX_NULLSTR cx_mutstr(NULL) + /** Expands a UCX string as printf arguments. */ #define CX_SFMT(s) (int) (s).length, (s).ptr @@ -419,17 +422,17 @@ * @note It is guaranteed that there is only one allocation for the * resulting string. * It is also guaranteed that the returned string is zero-terminated. + * If allocation fails, the @c ptr in the returned string will be @c NULL. * * @param alloc the allocator to use * @param str the string the other strings shall be concatenated to * @param count the number of the other following strings to concatenate * @param ... all other UCX strings - * @retval zero success - * @retval non-zero allocation failure + * @return the concatenated string */ cx_attr_nonnull -CX_EXPORT int cx_strcat_a(const CxAllocator *alloc, - cxmutstr *str, size_t count, ...); +CX_EXPORT cxmutstr cx_strcat_a(const CxAllocator *alloc, + cxmutstr str, size_t count, ...); /** * Concatenates strings and returns a new string. @@ -440,12 +443,12 @@ * @note It is guaranteed that there is only one allocation for the * resulting string. * It is also guaranteed that the returned string is zero-terminated. + * If allocation fails, the @c ptr in the returned string will be @c NULL. * * @param str (@c cxmutstr*) the string the other strings shall be concatenated to * @param count (@c size_t) the number of the other following strings to concatenate * @param ... all other UCX strings - * @retval zero success - * @retval non-zero allocation failure + * @return the concatenated string */ #define cx_strcat(str, count, ...) \ cx_strcat_a(cxDefaultAllocator, str, count, __VA_ARGS__) diff -r cf19b7820ff0 -r 94360453bce4 src/json.c --- a/src/json.c Thu Dec 25 12:07:37 2025 +0100 +++ b/src/json.c Sun Dec 28 14:10:14 2025 +0100 @@ -96,20 +96,16 @@ } static CxJsonToken token_create(CxJson *json, bool isstring, size_t start, size_t end) { - cxmutstr buf_str = cx_mutstrn(json->buffer.space + start, end - start); - cxmutstr str; - bool allocated; + cxmutstr str = cx_mutstrn(json->buffer.space + start, end - start); + bool allocated = false; if (json->uncompleted_tokentype != CX_JSON_NO_TOKEN) { allocated = true; - str = json->uncompleted_content; - if (cx_strcat(&str, 1, buf_str)) { - return (CxJsonToken){CX_JSON_NO_TOKEN, false, {NULL, 0}}; // LCOV_EXCL_LINE + str = cx_strcat(json->uncompleted_content, 1, str); + if (str.ptr == NULL) { + return (CxJsonToken){CX_JSON_NO_TOKEN, false, CX_NULLSTR}; // LCOV_EXCL_LINE } - json->uncompleted_content = (cxmutstr){NULL, 0}; + json->uncompleted_content = CX_NULLSTR; json->uncompleted_tokentype = CX_JSON_NO_TOKEN; - } else { - allocated = false; - str = buf_str; } CxJsonTokenType ttype; if (isstring) { @@ -126,7 +122,7 @@ if (allocated) { cx_strfree(&str); } - return (CxJsonToken){CX_JSON_TOKEN_ERROR, false, {NULL, 0}}; + return (CxJsonToken){CX_JSON_TOKEN_ERROR, false, CX_NULLSTR}; } return (CxJsonToken){ttype, allocated, str}; } @@ -193,7 +189,7 @@ } else if (ctype != CX_JSON_NO_TOKEN) { // single-char token json->buffer.pos = i + 1; - *result = (CxJsonToken){ctype, false, {NULL, 0}}; + *result = (CxJsonToken){ctype, false, CX_NULLSTR}; return CX_JSON_NO_ERROR; } else { ttype = CX_JSON_TOKEN_LITERAL; // number or literal @@ -245,11 +241,13 @@ } json->uncompleted_tokentype = ttype; } else { - // previously we also had an uncompleted token + // previously we already had an uncompleted token // combine the uncompleted token with the current token - if (cx_strcat(&json->uncompleted_content, 1, uncompleted)) { + cxmutstr s = cx_strcat(json->uncompleted_content, 1, uncompleted); + if (s.ptr == NULL) { return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE } + json->uncompleted_content = s; } // advance the buffer position - we saved the stuff in the uncompleted token json->buffer.pos += uncompleted.length; @@ -1429,14 +1427,14 @@ CxBuffer buffer; if (cxBufferInit(&buffer, allocator, NULL, 128, CX_BUFFER_AUTO_EXTEND | CX_BUFFER_DO_NOT_FREE)) { - return (cxmutstr){NULL, 0}; + return CX_NULLSTR; } if (cx_json_write_rec(&buffer, value, cxBufferWriteFunc, writer, 0) || cxBufferTerminate(&buffer)) { // LCOV_EXCL_START buffer.flags &= ~CX_BUFFER_DO_NOT_FREE; cxBufferDestroy(&buffer); - return (cxmutstr){NULL, 0}; + return CX_NULLSTR; // LCOV_EXCL_STOP } else { cxmutstr str = cx_bstr_m(&buffer); diff -r cf19b7820ff0 -r 94360453bce4 src/string.c --- a/src/string.c Thu Dec 25 12:07:37 2025 +0100 +++ b/src/string.c Sun Dec 28 14:10:14 2025 +0100 @@ -99,13 +99,15 @@ return size; } -int cx_strcat_a( +cxmutstr cx_strcat_a( const CxAllocator *alloc, - cxmutstr *str, + cxmutstr str, size_t count, ... ) { - if (count == 0) return 0; + if (count == 0) { + return cx_strdup_a(alloc, str); + } va_list ap; va_start(ap, count); va_list ap2; @@ -113,7 +115,7 @@ // compute overall length bool overflow = false; - size_t slen = str->length; + size_t slen = str.length; for (size_t i = 0; i < count; i++) { cxstring s = va_arg(ap, cxstring); if (slen > SIZE_MAX - s.length) overflow = true; @@ -125,31 +127,31 @@ if (overflow) { va_end(ap2); errno = EOVERFLOW; - return -1; + return CX_NULLSTR; } - // reallocate or create new string - if (cxReallocate(alloc, &str->ptr, slen + 1)) { + // reallocate or create a new string + if (cxReallocate(alloc, &str.ptr, slen + 1)) { // LCOV_EXCL_START va_end(ap2); - return -1; + return CX_NULLSTR; // LCOV_EXCL_STOP } // concatenate strings - size_t pos = str->length; - str->length = slen; + size_t pos = str.length; + str.length = slen; for (size_t i = 0; i < count; i++) { cxstring s = va_arg(ap2, cxstring); - memcpy(str->ptr + pos, s.ptr, s.length); + memcpy(str.ptr + pos, s.ptr, s.length); pos += s.length; } va_end(ap2); // terminate string - str->ptr[str->length] = '\0'; + str.ptr[str.length] = '\0'; - return 0; + return str; } cxstring cx_strsubs( diff -r cf19b7820ff0 -r 94360453bce4 tests/test_string.c --- a/tests/test_string.c Thu Dec 25 12:07:37 2025 +0100 +++ b/tests/test_string.c Sun Dec 28 14:10:14 2025 +0100 @@ -401,26 +401,22 @@ CxAllocator *alloc = &talloc.base; CX_TEST_DO { - cxmutstr t1 = {0}; - CX_TEST_ASSERT(0 == cx_strcat_a(alloc, &t1, 2, s1, s2)); + cxmutstr t1 = cx_strcat_a(alloc, CX_NULLSTR, 2, s1, s2); CX_TEST_ASSERT(0 == cx_strcmp(t1, "1234")); ASSERT_ZERO_TERMINATED(t1); cx_strfree_a(alloc, &t1); - cxmutstr t2 = {0}; - CX_TEST_ASSERT(0 == cx_strcat_a(alloc, &t2, 3, s1, s2, s3)); + cxmutstr t2 = cx_strcat_a(alloc, CX_NULLSTR, 3, s1, s2, s3); CX_TEST_ASSERT(0 == cx_strcmp(t2, "123456")); ASSERT_ZERO_TERMINATED(t2); cx_strfree_a(alloc, &t2); - cxmutstr t3 = {0}; - CX_TEST_ASSERT(0 == cx_strcat_a(alloc, &t3, 6, s1, sn, s2, sn, s3, sn)); + cxmutstr t3 = cx_strcat_a(alloc, CX_NULLSTR, 6, s1, sn, s2, sn, s3, sn); CX_TEST_ASSERT(0 == cx_strcmp(t3, "123456")); ASSERT_ZERO_TERMINATED(t3); cx_strfree_a(alloc, &t3); - cxmutstr t4 = {0}; - CX_TEST_ASSERT(0 == cx_strcat_a(alloc, &t4, 2, sn, sn)); + cxmutstr t4 = cx_strcat_a(alloc, CX_NULLSTR, 2, sn, sn); CX_TEST_ASSERT(0 == cx_strcmp(t4, "")); ASSERT_ZERO_TERMINATED(t4); cx_strfree_a(alloc, &t4); @@ -428,15 +424,14 @@ CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); // use the macro - cxmutstr t5 = {0}; - CX_TEST_ASSERT(0 == cx_strcat(&t5, 3, s3, s1, s2)); + cxmutstr t5 = cx_strcat(CX_NULLSTR, 3, s3, s1, s2); CX_TEST_ASSERT(0 == cx_strcmp(t5, "561234")); ASSERT_ZERO_TERMINATED(t5); cx_strfree(&t5); // use an initial string - cxmutstr t6 = cx_strdup(cx_str("Hello")); - CX_TEST_ASSERT(0 == cx_strcat(&t6, 2, cx_str(", "), cx_str("World!"))); + cxmutstr t6 = cx_strdup("Hello"); + t6 = cx_strcat(t6, 2, cx_str(", "), cx_str("World!")); CX_TEST_ASSERT(0 == cx_strcmp(t6, "Hello, World!")); ASSERT_ZERO_TERMINATED(t6); cx_strfree(&t6); @@ -447,8 +442,7 @@ cxstring b = cx_strn(fakestr, SIZE_MAX / 3 - 5); cxstring c = cx_strn(fakestr, SIZE_MAX / 3 + 20); errno = 0; - cxmutstr z = {0}; - CX_TEST_ASSERT(0 != cx_strcat(&z, 3, a, b, c)); + cxmutstr z = cx_strcat(CX_NULLSTR, 3, a, b, c); CX_TEST_ASSERT(errno == EOVERFLOW); CX_TEST_ASSERT(z.ptr == NULL); CX_TEST_ASSERT(z.length == 0); @@ -468,8 +462,7 @@ cxstring s9 = cx_str("xy"); CX_TEST_DO { - cxmutstr r = {0}; - CX_TEST_ASSERT(0 == cx_strcat(&r, 9, s1, s2, s3, s4, s5, s6, s7, s8, s9)); + cxmutstr r = cx_strcat(CX_NULLSTR, 9, s1, s2, s3, s4, s5, s6, s7, s8, s9); CX_TEST_ASSERT(0 == cx_strcmp(r, "123456789abcdef0xy")); ASSERT_ZERO_TERMINATED(r); cx_strfree(&r);