Tue, 16 Jan 2024 23:13:01 +0100
add cx_sprintf() variants - fixes #353
CHANGELOG | file | annotate | diff | comparison | revisions | |
src/cx/printf.h | file | annotate | diff | comparison | revisions | |
src/printf.c | file | annotate | diff | comparison | revisions | |
tests/test_printf.c | file | annotate | diff | comparison | revisions |
1.1 --- a/CHANGELOG Tue Jan 16 23:12:43 2024 +0100 1.2 +++ b/CHANGELOG Tue Jan 16 23:13:01 2024 +0100 1.3 @@ -4,6 +4,7 @@ 1.4 * adds cxListFindRemove() 1.5 * adds cxBufferReset() 1.6 * adds cx_cmp_ptr() 1.7 + * adds cx_sprintf() and several more variants 1.8 * adds runtime constants to read out the actual SBO sizes 1.9 * adds improved version of UCX 2 Test framework (now a self-contained header) 1.10 * the cx_compare_func symbol is now also declared by compare.h
2.1 --- a/src/cx/printf.h Tue Jan 16 23:12:43 2024 +0100 2.2 +++ b/src/cx/printf.h Tue Jan 16 23:13:01 2024 +0100 2.3 @@ -164,6 +164,170 @@ 2.4 #define cx_bprintf(buffer, fmt, ...) cx_fprintf((CxBuffer*)buffer, \ 2.5 (cx_write_func) cxBufferWrite, fmt, __VA_ARGS__) 2.6 2.7 + 2.8 +/** 2.9 + * An \c sprintf like function which reallocates the string when the buffer is not large enough. 2.10 + * 2.11 + * \note The resulting string is guaranteed to be zero-terminated. 2.12 + * That means, when the buffer needed to be reallocated, the new size of the buffer will be 2.13 + * the length returned by this function plus one. 2.14 + * 2.15 + * @param str a pointer to the string buffer 2.16 + * @param len the current length of the buffer 2.17 + * @param fmt the format string 2.18 + * @param ... additional arguments 2.19 + * @return the length of produced string 2.20 + */ 2.21 +#define cx_sprintf(str, len, fmt, ...) cx_sprintf_a(cxDefaultAllocator, str, len, fmt, __VA_ARGS__) 2.22 + 2.23 +/** 2.24 + * An \c sprintf like function which reallocates the string when the buffer is not large enough. 2.25 + * 2.26 + * \note The resulting string is guaranteed to be zero-terminated. 2.27 + * That means, when the buffer needed to be reallocated, the new size of the buffer will be 2.28 + * the length returned by this function plus one. 2.29 + * 2.30 + * \attention The original buffer MUST have been allocated with the same allocator! 2.31 + * 2.32 + * @param alloc the allocator to use 2.33 + * @param str a pointer to the string buffer 2.34 + * @param len the current length of the buffer 2.35 + * @param fmt the format string 2.36 + * @param ... additional arguments 2.37 + * @return the length of produced string 2.38 + */ 2.39 +__attribute__((__nonnull__(1, 2, 4), __format__(printf, 4, 5))) 2.40 +int cx_sprintf_a(CxAllocator *alloc, char **str, size_t len, const char *fmt, ... ); 2.41 + 2.42 + 2.43 +/** 2.44 + * An \c sprintf like function which reallocates the string when the buffer is not large enough. 2.45 + * 2.46 + * \note The resulting string is guaranteed to be zero-terminated. 2.47 + * That means, when the buffer needed to be reallocated, the new size of the buffer will be 2.48 + * the length returned by this function plus one. 2.49 + * 2.50 + * @param str a pointer to the string buffer 2.51 + * @param len the current length of the buffer 2.52 + * @param fmt the format string 2.53 + * @param ap argument list 2.54 + * @return the length of produced string 2.55 + */ 2.56 +#define cx_vsprintf(str, len, fmt, ap) cx_vsprintf_a(cxDefaultAllocator, str, len, fmt, ap) 2.57 + 2.58 +/** 2.59 + * An \c sprintf like function which reallocates the string when the buffer is not large enough. 2.60 + * 2.61 + * \note The resulting string is guaranteed to be zero-terminated. 2.62 + * That means, when the buffer needed to be reallocated, the new size of the buffer will be 2.63 + * the length returned by this function plus one. 2.64 + * 2.65 + * \attention The original buffer MUST have been allocated with the same allocator! 2.66 + * 2.67 + * @param alloc the allocator to use 2.68 + * @param str a pointer to the string buffer 2.69 + * @param len the current length of the buffer 2.70 + * @param fmt the format string 2.71 + * @param ap argument list 2.72 + * @return the length of produced string 2.73 + */ 2.74 +__attribute__((__nonnull__)) 2.75 +int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t len, const char *fmt, va_list ap); 2.76 + 2.77 + 2.78 +/** 2.79 + * An \c sprintf like function which allocates a new string when the buffer is not large enough. 2.80 + * 2.81 + * The location of the resulting string will \em always be stored to \p str. When the buffer 2.82 + * was sufficiently large, \p buf itself will be stored to the location of \p str. 2.83 + * 2.84 + * \note The resulting string is guaranteed to be zero-terminated. 2.85 + * That means, when the buffer needed to be reallocated, the new size of the buffer will be 2.86 + * the length returned by this function plus one. 2.87 + * 2.88 + * \remark When a new string needed to be allocated, the contents of \p buf will be 2.89 + * poisoned after the call, because this function tries to produce the string in \p buf, first. 2.90 + * 2.91 + * @param buf a pointer to the buffer 2.92 + * @param len the length of the buffer 2.93 + * @param str a pointer to the location 2.94 + * @param fmt the format string 2.95 + * @param ... additional arguments 2.96 + * @return the length of produced string 2.97 + */ 2.98 +#define cx_sprintf_s(buf, len, str, fmt, ...) cx_sprintf_sa(cxDefaultAllocator, buf, len, str, fmt, __VA_ARGS__) 2.99 + 2.100 +/** 2.101 + * An \c sprintf like function which allocates a new string when the buffer is not large enough. 2.102 + * 2.103 + * The location of the resulting string will \em always be stored to \p str. When the buffer 2.104 + * was sufficiently large, \p buf itself will be stored to the location of \p str. 2.105 + * 2.106 + * \note The resulting string is guaranteed to be zero-terminated. 2.107 + * That means, when the buffer needed to be reallocated, the new size of the buffer will be 2.108 + * the length returned by this function plus one. 2.109 + * 2.110 + * \remark When a new string needed to be allocated, the contents of \p buf will be 2.111 + * poisoned after the call, because this function tries to produce the string in \p buf, first. 2.112 + * 2.113 + * @param alloc the allocator to use 2.114 + * @param buf a pointer to the buffer 2.115 + * @param len the length of the buffer 2.116 + * @param str a pointer to the location 2.117 + * @param fmt the format string 2.118 + * @param ... additional arguments 2.119 + * @return the length of produced string 2.120 + */ 2.121 +__attribute__((__nonnull__(1, 2, 4, 5), __format__(printf, 5, 6))) 2.122 +int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t len, char **str, const char *fmt, ... ); 2.123 + 2.124 +/** 2.125 + * An \c sprintf like function which allocates a new string when the buffer is not large enough. 2.126 + * 2.127 + * The location of the resulting string will \em always be stored to \p str. When the buffer 2.128 + * was sufficiently large, \p buf itself will be stored to the location of \p str. 2.129 + * 2.130 + * \note The resulting string is guaranteed to be zero-terminated. 2.131 + * That means, when the buffer needed to be reallocated, the new size of the buffer will be 2.132 + * the length returned by this function plus one. 2.133 + * 2.134 + * \remark When a new string needed to be allocated, the contents of \p buf will be 2.135 + * poisoned after the call, because this function tries to produce the string in \p buf, first. 2.136 + * 2.137 + * @param buf a pointer to the buffer 2.138 + * @param len the length of the buffer 2.139 + * @param str a pointer to the location 2.140 + * @param fmt the format string 2.141 + * @param ap argument list 2.142 + * @return the length of produced string 2.143 + */ 2.144 +#define cx_vsprintf_s(buf, len, str, fmt, ap) cx_vsprintf_sa(cxDefaultAllocator, buf, len, str, fmt, ap) 2.145 + 2.146 +/** 2.147 + * An \c sprintf like function which allocates a new string when the buffer is not large enough. 2.148 + * 2.149 + * The location of the resulting string will \em always be stored to \p str. When the buffer 2.150 + * was sufficiently large, \p buf itself will be stored to the location of \p str. 2.151 + * 2.152 + * \note The resulting string is guaranteed to be zero-terminated. 2.153 + * That means, when the buffer needed to be reallocated, the new size of the buffer will be 2.154 + * the length returned by this function plus one. 2.155 + * 2.156 + * \remark When a new string needed to be allocated, the contents of \p buf will be 2.157 + * poisoned after the call, because this function tries to produce the string in \p buf, first. 2.158 + * 2.159 + * @param alloc the allocator to use 2.160 + * @param buf a pointer to the buffer 2.161 + * @param len the length of the buffer 2.162 + * @param str a pointer to the location 2.163 + * @param fmt the format string 2.164 + * @param ap argument list 2.165 + * @return the length of produced string 2.166 + */ 2.167 +__attribute__((__nonnull__)) 2.168 +int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t len, char **str, const char *fmt, va_list ap); 2.169 + 2.170 + 2.171 #ifdef __cplusplus 2.172 } // extern "C" 2.173 #endif
3.1 --- a/src/printf.c Tue Jan 16 23:12:43 2024 +0100 3.2 +++ b/src/printf.c Tue Jan 16 23:13:01 2024 +0100 3.3 @@ -127,3 +127,73 @@ 3.4 return s; 3.5 } 3.6 3.7 +int cx_sprintf_a(CxAllocator *alloc, char **str, size_t len, const char *fmt, ... ) { 3.8 + va_list ap; 3.9 + va_start(ap, fmt); 3.10 + int ret = cx_vsprintf_a(alloc, str, len, fmt, ap); 3.11 + va_end(ap); 3.12 + return ret; 3.13 +} 3.14 + 3.15 +int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t len, const char *fmt, va_list ap) { 3.16 + va_list ap2; 3.17 + va_copy(ap2, ap); 3.18 + int ret = vsnprintf(*str, len, fmt, ap); 3.19 + if (ret < 0 || ((unsigned)ret) < len) { 3.20 + va_end(ap2); 3.21 + return ret; 3.22 + } else { 3.23 + unsigned newlen = ret + 1; 3.24 + char *ptr = cxRealloc(alloc, *str, newlen); 3.25 + if (ptr) { 3.26 + int newret = vsnprintf(ptr, newlen, fmt, ap2); 3.27 + va_end(ap2); 3.28 + if (newret < 0) { 3.29 + cxFree(alloc, ptr); 3.30 + return ret; 3.31 + } else { 3.32 + *str = ptr; 3.33 + return newret; 3.34 + } 3.35 + } else { 3.36 + va_end(ap2); 3.37 + return ret; 3.38 + } 3.39 + } 3.40 +} 3.41 + 3.42 +int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t len, char **str, const char *fmt, ... ) { 3.43 + va_list ap; 3.44 + va_start(ap, fmt); 3.45 + int ret = cx_vsprintf_sa(alloc, buf, len, str, fmt, ap); 3.46 + va_end(ap); 3.47 + return ret; 3.48 +} 3.49 + 3.50 +int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t len, char **str, const char *fmt, va_list ap) { 3.51 + va_list ap2; 3.52 + va_copy(ap2, ap); 3.53 + int ret = vsnprintf(buf, len, fmt, ap); 3.54 + *str = buf; 3.55 + if (ret < 0 || ((unsigned)ret) < len) { 3.56 + va_end(ap2); 3.57 + return ret; 3.58 + } else { 3.59 + unsigned newlen = ret + 1; 3.60 + char *ptr = cxMalloc(alloc, newlen); 3.61 + if (ptr) { 3.62 + int newret = vsnprintf(ptr, newlen, fmt, ap2); 3.63 + va_end(ap2); 3.64 + if (newret < 0) { 3.65 + cxFree(alloc, ptr); 3.66 + return ret; 3.67 + } else { 3.68 + *str = ptr; 3.69 + return newret; 3.70 + } 3.71 + } else { 3.72 + va_end(ap2); 3.73 + return ret; 3.74 + } 3.75 + } 3.76 +}
4.1 --- a/tests/test_printf.c Tue Jan 16 23:12:43 2024 +0100 4.2 +++ b/tests/test_printf.c Tue Jan 16 23:13:01 2024 +0100 4.3 @@ -294,6 +294,107 @@ 4.4 free(expected); 4.5 } 4.6 4.7 +CX_TEST(test_sprintf_no_realloc) { 4.8 + char *buf = malloc(16); 4.9 + CxTestingAllocator talloc; 4.10 + cx_testing_allocator_init(&talloc); 4.11 + CxAllocator *alloc = &talloc.base; 4.12 + CX_TEST_DO { 4.13 + char *oldbuf = buf; 4.14 + size_t len = cx_sprintf_a(alloc, &buf, 16, "Test %d %s", 47, "string"); 4.15 + CX_TEST_ASSERT(oldbuf == buf); 4.16 + CX_TEST_ASSERT(len == 14); 4.17 + CX_TEST_ASSERT(0 == memcmp(buf, "Test 47 string", 15)); 4.18 + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 4.19 + } 4.20 + cx_testing_allocator_destroy(&talloc); 4.21 + free(buf); 4.22 +} 4.23 + 4.24 +CX_TEST(test_sprintf_realloc) { 4.25 + CxTestingAllocator talloc; 4.26 + cx_testing_allocator_init(&talloc); 4.27 + CxAllocator *alloc = &talloc.base; 4.28 + char *buf = cxMalloc(alloc, 8); 4.29 + CX_TEST_DO { 4.30 + size_t len = cx_sprintf_a(alloc, &buf, 8, "Test %d %s", 47, "foobar"); 4.31 + CX_TEST_ASSERT(len == 14); 4.32 + CX_TEST_ASSERT(0 == memcmp(buf, "Test 47 foobar", 15)); 4.33 + cxFree(alloc, buf); 4.34 + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 4.35 + } 4.36 + cx_testing_allocator_destroy(&talloc); 4.37 +} 4.38 + 4.39 +CX_TEST(test_sprintf_realloc_to_fit_terminator) { 4.40 + CxTestingAllocator talloc; 4.41 + cx_testing_allocator_init(&talloc); 4.42 + CxAllocator *alloc = &talloc.base; 4.43 + // make it so that only the zero-terminator does not fit 4.44 + char *buf = cxMalloc(alloc, 14); 4.45 + CX_TEST_DO { 4.46 + size_t len = cx_sprintf_a(alloc, &buf, 14, "Test %d %s", 13, "string"); 4.47 + CX_TEST_ASSERT(len == 14); 4.48 + CX_TEST_ASSERT(0 == memcmp(buf, "Test 13 string", 15)); 4.49 + cxFree(alloc, buf); 4.50 + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 4.51 + } 4.52 + cx_testing_allocator_destroy(&talloc); 4.53 +} 4.54 + 4.55 +CX_TEST(test_sprintf_s_no_alloc) { 4.56 + char buf[16]; 4.57 + CxTestingAllocator talloc; 4.58 + cx_testing_allocator_init(&talloc); 4.59 + CxAllocator *alloc = &talloc.base; 4.60 + CX_TEST_DO { 4.61 + char *str; 4.62 + size_t len = cx_sprintf_sa(alloc, buf, 16, &str, "Test %d %s", 47, "string"); 4.63 + CX_TEST_ASSERT(str == buf); 4.64 + CX_TEST_ASSERT(len == 14); 4.65 + CX_TEST_ASSERT(0 == memcmp(buf, "Test 47 string", 15)); 4.66 + CX_TEST_ASSERT(0 == memcmp(str, "Test 47 string", 15)); 4.67 + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 4.68 + } 4.69 + cx_testing_allocator_destroy(&talloc); 4.70 +} 4.71 + 4.72 +CX_TEST(test_sprintf_s_alloc) { 4.73 + char buf[16]; 4.74 + memcpy(buf, "0123456789abcdef", 16); 4.75 + CxTestingAllocator talloc; 4.76 + cx_testing_allocator_init(&talloc); 4.77 + CxAllocator *alloc = &talloc.base; 4.78 + CX_TEST_DO { 4.79 + char *str; 4.80 + size_t len = cx_sprintf_sa(alloc, buf, 16, &str, "Hello %d %s", 4711, "larger string"); 4.81 + CX_TEST_ASSERT(str != buf); 4.82 + CX_TEST_ASSERT(len == 24); 4.83 + CX_TEST_ASSERT(0 == memcmp(str, "Hello 4711 larger string", 25)); 4.84 + cxFree(alloc, str); 4.85 + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 4.86 + } 4.87 + cx_testing_allocator_destroy(&talloc); 4.88 +} 4.89 + 4.90 +CX_TEST(test_sprintf_s_alloc_to_fit_terminator) { 4.91 + char buf[16]; 4.92 + memcpy(buf, "0123456789abcdef", 16); 4.93 + CxTestingAllocator talloc; 4.94 + cx_testing_allocator_init(&talloc); 4.95 + CxAllocator *alloc = &talloc.base; 4.96 + CX_TEST_DO { 4.97 + char *str; 4.98 + size_t len = cx_sprintf_sa(alloc, buf, 16, &str, "Hello %d %s", 112, "string"); 4.99 + CX_TEST_ASSERT(str != buf); 4.100 + CX_TEST_ASSERT(len == 16); 4.101 + CX_TEST_ASSERT(0 == memcmp(str, "Hello 112 string", 17)); // include terminator 4.102 + cxFree(alloc, str); 4.103 + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 4.104 + } 4.105 + cx_testing_allocator_destroy(&talloc); 4.106 +} 4.107 + 4.108 CxTestSuite *cx_test_suite_printf(void) { 4.109 CxTestSuite *suite = cx_test_suite_new("printf"); 4.110 4.111 @@ -303,6 +404,12 @@ 4.112 cx_test_register(suite, test_fprintf); 4.113 cx_test_register(suite, test_asprintf); 4.114 cx_test_register(suite, test_asprintf_large_string); 4.115 + cx_test_register(suite, test_sprintf_no_realloc); 4.116 + cx_test_register(suite, test_sprintf_realloc); 4.117 + cx_test_register(suite, test_sprintf_realloc_to_fit_terminator); 4.118 + cx_test_register(suite, test_sprintf_s_no_alloc); 4.119 + cx_test_register(suite, test_sprintf_s_alloc); 4.120 + cx_test_register(suite, test_sprintf_s_alloc_to_fit_terminator); 4.121 4.122 return suite; 4.123 }