From 67e9c8ff06432fea0c73e54e37c9c1f0e13af17e Mon Sep 17 00:00:00 2001 From: Patrick Pelissier Date: Tue, 28 May 2024 23:05:54 +0200 Subject: [PATCH] New internal string format: * maximum size of a string is 2^32-1 now by default on a 64 bits system (can be increased using M_USE_STRING_LARGE_INDEX if needed). * This reduce consumption of a string_t from 24 bytes to 16 bytes. * Change the stack representation of a string, so that it lets 15 allocated bytes for the user even in 64 bytes mode. For 32 bits system, it increases to 15 allocated byte for stack representation. * synthetic performance benchmark shows a little bit of decrease of performance. But performance in real application shows a significant increase (probably due to the reduced size of the type). *Augment test suite to be sure to test all corner cases of the new representaiton. --- m-serial-json.h | 16 +-- m-string.h | 248 ++++++++++++++++++++++++++++--------------- tests/test-mstring.c | 30 +++++- 3 files changed, 198 insertions(+), 96 deletions(-) diff --git a/m-serial-json.h b/m-serial-json.h index 52e2d4f4..38b9855d 100644 --- a/m-serial-json.h +++ b/m-serial-json.h @@ -71,15 +71,17 @@ m_ser1al_json_write_float(m_serial_write_t serial, const long double data, const M_INLINE m_serial_return_code_t m_ser1al_json_write_string(m_serial_write_t serial, const char data[], size_t length) { + M_ASSERT(length == (m_str1ng_size_t) length); M_ASSERT_SLOW(length == strlen(data) ); FILE *f = (FILE *)serial->data[0].p; M_ASSERT(f != NULL && data != NULL); /* HACK: Build dummy string to reuse m_string_out_str */ m_string_t v2; uintptr_t ptr = (uintptr_t) data; - v2->u.heap.size = length; - v2->u.heap.alloc = length + 1; - v2->ptr = (char*)ptr; + v2->u.heap.size = (m_str1ng_size_t) length; + v2->u.heap.alloc[sizeof(m_str1ng_size_t)-2] = 1; + v2->u.heap.alloc[sizeof(m_str1ng_size_t)-1] = 31; + v2->u.heap.ptr = (char*)ptr; m_string_out_str(f, v2); return M_UNLIKELY(ferror(f)) ? m_core_serial_fail() : M_SERIAL_OK_DONE; } @@ -620,15 +622,17 @@ m_ser1al_str_json_write_float(m_serial_write_t serial, const long double data, c M_INLINE m_serial_return_code_t m_ser1al_str_json_write_string(m_serial_write_t serial, const char data[], size_t length) { + M_ASSERT(length == (m_str1ng_size_t) length); M_ASSERT_SLOW(length == strlen(data) ); struct m_string_s *f = (struct m_string_s *)serial->data[0].p; M_ASSERT(f != NULL && data != NULL); /* HACK: Build dummy string to reuse m_string_get_str */ m_string_t v2; uintptr_t ptr = (uintptr_t) data; - v2->u.heap.size = length; - v2->u.heap.alloc = length + 1; - v2->ptr = (char*)ptr; + v2->u.heap.size = (m_str1ng_size_t) length; + v2->u.heap.alloc[sizeof(m_str1ng_size_t)-2] = 1; + v2->u.heap.alloc[sizeof(m_str1ng_size_t)-1] = 31; + v2->u.heap.ptr = (char*)ptr; m_string_get_str(f, v2, true); return M_SERIAL_OK_DONE; } diff --git a/m-string.h b/m-string.h index fc0ff412..fbf94546 100644 --- a/m-string.h +++ b/m-string.h @@ -40,22 +40,39 @@ M_BEGIN_PROTECTED_CODE M_ASSERT_SLOW (m_string_size(v) == strlen(m_string_get_cstr(v))); \ M_ASSERT (m_string_get_cstr(v)[m_string_size(v)] == 0); \ M_ASSERT (m_string_size(v) < m_string_capacity(v)); \ - M_ASSERT (m_string_capacity(v) < sizeof (m_str1ng_heap_ct) || !m_str1ng_stack_p(v)); \ } while(0) -// string if it is heap allocated +/* Define the maximum size of a string + By default it is a little bit less than 2^32 bytes, which should be enough for + most programs. Otherwise it can be increased. */ +#ifdef M_USE_STRING_LARGE_INDEX +typedef uint64_t m_str1ng_size_t; +#define m_str1ng_size_clz m_core_clz64 +#else +typedef uint32_t m_str1ng_size_t; +#define m_str1ng_size_clz m_core_clz32 +#endif + +// The string representation if it heap allocated typedef struct m_str1ng_heap_s { - size_t size; - size_t alloc; + char *ptr; + m_str1ng_size_t size; + unsigned char alloc[sizeof (m_str1ng_size_t)]; } m_str1ng_heap_ct; -// string if it is stack allocated -typedef struct m_str1ng_stack_s { - char buffer[sizeof (m_str1ng_heap_ct)]; -} m_str1ng_stack_ct; -// both cases of string are possible + +/* Define the union between a heap allocated string and its inline representation + If buffer[sizeof(m_str1ng_heap_ct)-2] == 0 then + It is a stack based string where buffer is the string, with a maximum allocation of 15 bytes + In this case buffer[sizeof(m_str1ng_heap_ct)-1] is the size of the string + Otherwise it is a heap representation then + ptr is the pointer to the data, size its size, and alloc a representation of the capacity: + alloc[sizeof (m_str1ng_size_t)-2] << alloc[sizeof (m_str1ng_size_t)-1] + we shall have alloc[sizeof (m_str1ng_size_t)-2] == buffer[sizeof(m_str1ng_heap_ct)-2] + so that in case of heap representation, the field buffer[sizeof(m_str1ng_heap_ct)-2] is != 0 +*/ typedef union m_str1ng_union_u { - m_str1ng_heap_ct heap; - m_str1ng_stack_ct stack; + m_str1ng_heap_ct heap; + char buffer[sizeof (m_str1ng_heap_ct)]; } m_str1ng_union_ct; /* State of the UTF8 decoding machine state */ @@ -101,7 +118,6 @@ typedef enum { /* This is the main structure of this module, representing a dynamic string */ typedef struct m_string_s { m_str1ng_union_ct u; - char *ptr; } m_string_t[1]; // Pointer to a Dynamic string @@ -138,22 +154,15 @@ typedef struct m_string_it_s { */ /* Internal method to test if the string is stack based or heap based - We test if the ptr is NULL or not. - This is not particularly efficient from a memory point of view - (as we could reuse the pointer field to store some data) - but it should be enough for most of "small" strings as most - of the strings are less than 14 characters (64 bits architecture). - Moreover it generates quite efficient code. - NOTE: We set the pointer to NULL (instead of storing a pointer to - the stack buffer for example) for stack based allocation so that - the string structure remains trivially movable (which is an important - property to have). + Stack representation ensures that the penultimate byte is 0. + By encoding, we ensure that the penultimate byte of 'alloc' (of the heap representation) + cannot be 0. */ M_INLINE bool m_str1ng_stack_p(const m_string_t s) { // Function can be called when contract is not fulfilled - return (s->ptr == NULL); + return (s->u.buffer[sizeof(m_str1ng_heap_ct)-2] == 0); } /* Internal method to set the size of the string (excluding final nul char) */ @@ -164,9 +173,10 @@ m_str1ng_set_size(m_string_t s, size_t size) if (m_str1ng_stack_p(s)) { M_ASSERT (size < sizeof (m_str1ng_heap_ct) - 1); // The size of the string is stored as the last char of the buffer. - s->u.stack.buffer[sizeof (m_str1ng_heap_ct) - 1] = (char) size; + s->u.buffer[sizeof(m_str1ng_heap_ct)-1] = (char) size; } else { - s->u.heap.size = size; + M_ASSUME(size == (m_str1ng_size_t) size); + s->u.heap.size = (m_str1ng_size_t) size; } } @@ -175,8 +185,7 @@ M_INLINE size_t m_string_size(const m_string_t s) { // Function can be called when contract is not fulfilled - // Reading both values before calling the '?' operator enables compiler to generate branchless code - const size_t s_stack = (size_t) s->u.stack.buffer[sizeof (m_str1ng_heap_ct) - 1]; + const size_t s_stack = (size_t) s->u.buffer[sizeof(m_str1ng_heap_ct)-1]; const size_t s_heap = s->u.heap.size; return m_str1ng_stack_p(s) ? s_stack : s_heap; } @@ -186,19 +195,67 @@ M_INLINE size_t m_string_capacity(const m_string_t s) { // Function can be called when contract is not fulfilled - // Reading both values before calling the '?' operator enables compiler to generate branchless code const size_t c_stack = sizeof (m_str1ng_heap_ct) - 1; - const size_t c_heap = s->u.heap.alloc; + const size_t c_heap = (m_str1ng_size_t) s->u.heap.alloc[sizeof (m_str1ng_size_t)-2] << s->u.heap.alloc[sizeof (m_str1ng_size_t)-1]; return m_str1ng_stack_p(s) ? c_stack : c_heap; } +/* Internal function to round up the allocation size to the next allowed representation + Return the rounded allocation and the pair (m,e) used to encode it + An overflow occurs if the result is strictly lower than 'alloc' before casting. + */ +M_INLINE m_str1ng_size_t +m_str1ng_round_capacity(unsigned char *m, unsigned char *e, m_str1ng_size_t alloc) +{ + // We will encode 'alloc' as a number m<= 0xFE000001, rounding will overflow the max of a m_str1ng_size_t + and *m == 0. + This case should be handled by the caller as an overflow error + (return value is lower than input) + */ + *m = (unsigned char) ((alloc + ((1U <<(b-(CHAR_BIT-1)))-1))>>(b-(CHAR_BIT-1))); + *e = (unsigned char) (b - (CHAR_BIT-1)); + } + // Worst case is when the rounding overflow the size m_str1ng_size_t + M_ASSERT (((m_str1ng_size_t) *m << *e) >= alloc || *m == 0); + return (m_str1ng_size_t) *m << *e; +} + +/* Internal function to save the allocation size to the next allowed representation + as returned by m_str1ng_round_capacity */ +M_INLINE void +m_str1ng_set_capacity(m_string_t s, unsigned char m, unsigned char e) +{ + // Function can be called when contract is not fulfilled + M_ASSERT(m != 0); + s->u.heap.alloc[sizeof (m_str1ng_size_t)-2] = m; + s->u.heap.alloc[sizeof (m_str1ng_size_t)-1] = e; +} + /* Return a writable pointer to the array of char of the string */ M_INLINE char* m_str1ng_get_cstr(m_string_t v) { // Function can be called when contract is not fulfilled - char *const ptr_stack = &v->u.stack.buffer[0]; - char *const ptr_heap = v->ptr; + char *const ptr_stack = &v->u.buffer[0]; + char *const ptr_heap = v->u.heap.ptr; return m_str1ng_stack_p(v) ? ptr_stack : ptr_heap; } @@ -208,8 +265,8 @@ m_string_get_cstr(const m_string_t v) { // Function cannot be called when contract is not fulfilled // but it is called by contract (so no contract check to avoid infinite recursion). - const char *const ptr_stack = &v->u.stack.buffer[0]; - const char *const ptr_heap = v->ptr; + const char *const ptr_stack = &v->u.buffer[0]; + const char *const ptr_heap = v->u.heap.ptr; return m_str1ng_stack_p(v) ? ptr_stack : ptr_heap; } @@ -218,9 +275,7 @@ m_string_get_cstr(const m_string_t v) M_INLINE void m_string_init(m_string_t s) { - s->ptr = NULL; - s->u.stack.buffer[0] = 0; - m_str1ng_set_size(s, 0); + memset(s, 0, sizeof(m_string_t)); M_STR1NG_CONTRACT(s); } @@ -230,12 +285,13 @@ m_string_clear(m_string_t v) { M_STR1NG_CONTRACT(v); if (!m_str1ng_stack_p(v)) { - M_MEMORY_FREE(v->ptr); - v->ptr = NULL; + M_MEMORY_FREE(v->u.heap.ptr); + v->u.heap.ptr = NULL; } /* This is not needed but is safer to make the string invalid so that it can be detected. */ - v->u.stack.buffer[sizeof (m_str1ng_heap_ct) - 1] = CHAR_MAX; + v->u.buffer[sizeof(m_str1ng_heap_ct)-2] = 0; + v->u.buffer[sizeof(m_str1ng_heap_ct)-1] = CHAR_MAX; } M_INLINE m_string_ptr m_str1ng_init_ref(m_string_t v) { m_string_init(v); return v; } @@ -248,10 +304,10 @@ M_INLINE char * m_string_clear_get_cstr(m_string_t v) { M_STR1NG_CONTRACT(v); - char *p = v->ptr; + char *p = v->u.heap.ptr; if (m_str1ng_stack_p(v)) { // The string was stack allocated. - p = v->u.stack.buffer; + p = &v->u.buffer[0]; // Need to allocate a heap string to return the copy. size_t alloc = m_string_size(v)+1; char *ptr = M_MEMORY_REALLOC (char, NULL, alloc); @@ -263,8 +319,8 @@ m_string_clear_get_cstr(m_string_t v) memcpy(ptr, p, alloc); p = ptr; } - v->ptr = NULL; - v->u.stack.buffer[sizeof (m_str1ng_heap_ct) - 1] = CHAR_MAX; + v->u.buffer[sizeof(m_str1ng_heap_ct)-2] = 0; + v->u.buffer[sizeof(m_str1ng_heap_ct)-1] = CHAR_MAX; return p; } @@ -308,6 +364,7 @@ m_string_empty_p(const m_string_t v) Ensures that the string capacity is greater than size_alloc (size_alloc shall include the final null char). It may move the string from stack based to heap based. + The string 'v' no longer has a working size field. Return a pointer to the writable string. */ M_INLINE char * @@ -323,7 +380,9 @@ m_str1ng_fit2size (m_string_t v, size_t size_alloc) if (M_UNLIKELY (size_alloc > old_alloc)) { // Insufficient current allocation to store the new string // Perform an allocation on the heap. - size_t alloc = size_alloc + size_alloc / 2; + size_t alloc = (size_alloc + size_alloc / 2); + unsigned char m, e; + alloc = m_str1ng_round_capacity(&m, &e, (m_str1ng_size_t) alloc); if (M_UNLIKELY_NOMEM (alloc <= size_alloc)) { /* Overflow in alloc computation */ M_MEMORY_FULL(sizeof (char) * alloc); @@ -331,27 +390,28 @@ m_str1ng_fit2size (m_string_t v, size_t size_alloc) abort(); return NULL; } - char *ptr = M_MEMORY_REALLOC (char, v->ptr, alloc); + char *ptr = m_str1ng_stack_p(v) ? NULL : v->u.heap.ptr; + ptr = M_MEMORY_REALLOC (char, ptr, alloc); if (M_UNLIKELY_NOMEM (ptr == NULL)) { M_MEMORY_FULL(sizeof (char) * alloc); // NOTE: Return is currently broken. abort(); return NULL; } - // The pointer cannot be the stack buffer of the string. - // as it is heap allocated - M_ASSERT(ptr != &v->u.stack.buffer[0]); + // The pointer cannot be the stack buffer of the string as it is heap allocated + M_ASSERT(ptr != &v->u.buffer[0]); if (m_str1ng_stack_p(v)) { // The string was stack allocated. /* Copy the stack allocation into the new heap allocation */ - const size_t size = (size_t) v->u.stack.buffer[sizeof (m_str1ng_heap_ct) - 1] + 1U; - M_ASSERT( size <= alloc); - M_ASSERT( size <= sizeof (v->u.stack.buffer)-1); - memcpy(ptr, &v->u.stack.buffer[0], size); + const size_t size = (size_t) v->u.buffer[sizeof(m_str1ng_heap_ct) - 1]; + memcpy(ptr, &v->u.buffer[0], size+1); } // The string cannot be stack allocated anymore. - v->ptr = ptr; - v->u.heap.alloc = alloc; + v->u.heap.ptr = ptr; + m_str1ng_set_capacity(v, m, e); + // Size is not set as the function is called in context + // where the caller already know the final size of the string, + // so it will update it. return ptr; } return m_str1ng_get_cstr(v); @@ -374,32 +434,39 @@ m_string_reserve(m_string_t v, size_t alloc) // Allocation can fit in the stack space if (!m_str1ng_stack_p(v)) { /* Transform Heap Allocate to Stack Allocate */ - char *ptr = &v->u.stack.buffer[0]; - memcpy(ptr, v->ptr, size+1); - M_MEMORY_FREE(v->ptr); - v->ptr = NULL; - m_str1ng_set_size(v, size); + char *ptr = &v->u.buffer[0]; + char *oldptr = v->u.heap.ptr; + memcpy(ptr, oldptr, size+1); + M_MEMORY_FREE(oldptr); + v->u.buffer[sizeof(m_string_t)-2] = 0; + v->u.buffer[sizeof(m_string_t)-1] = (char) size; } else { /* Already a stack based alloc: nothing to do */ } } else { // Allocation cannot fit in the stack space // Need to allocate in heap space - // If the string is stack allocated, v->ptr is NULL - // and it will therefore perform the initial allocation - char *ptr = M_MEMORY_REALLOC (char, v->ptr, alloc); + unsigned char m, e; + size_t r_alloc = m_str1ng_round_capacity(&m, &e, (m_str1ng_size_t) alloc); + if (M_UNLIKELY_NOMEM (alloc > r_alloc)) { + M_MEMORY_FULL(sizeof (char) * alloc); + return; + } + char *ptr = m_str1ng_stack_p(v) ? NULL : v->u.heap.ptr; + ptr = M_MEMORY_REALLOC (char, ptr, r_alloc); if (M_UNLIKELY_NOMEM (ptr == NULL) ) { M_MEMORY_FULL(sizeof (char) * alloc); return; } if (m_str1ng_stack_p(v)) { // Copy from stack space to heap space the string - char *ptr_stack = &v->u.stack.buffer[0]; + char *ptr_stack = &v->u.buffer[0]; memcpy(ptr, ptr_stack, size+1); - v->u.heap.size = size; + M_ASSERT(size == (m_str1ng_size_t) size); + v->u.heap.size = (m_str1ng_size_t) size; } - v->ptr = ptr; - v->u.heap.alloc = alloc; + v->u.heap.ptr = ptr; + m_str1ng_set_capacity(v, m, e); } M_STR1NG_CONTRACT (v); } @@ -429,6 +496,7 @@ m_string_set_cstrn(m_string_t v, const char str[], size_t n) char *ptr = m_str1ng_fit2size(v, size+1); // The memcpy will not copy the final null char of the string memcpy(ptr, str, size); + // Cannot copy the final null char using memcpy ptr[size] = 0; m_str1ng_set_size(v, size); M_STR1NG_CONTRACT (v); @@ -460,6 +528,7 @@ m_string_set_n(m_string_t v, const m_string_t ref, size_t offset, size_t length) char *ptr = m_str1ng_fit2size(v, size+1); // v may be equal to ref, so a memmove is needed instead of a memcpy memmove(ptr, m_string_get_cstr(ref) + offset, size); + // Cannot copy the final null char using memcpy ptr[size] = 0; m_str1ng_set_size(v, size); M_STR1NG_CONTRACT (v); @@ -498,7 +567,8 @@ m_string_init_move(m_string_t v1, m_string_t v2) M_STR1NG_CONTRACT (v2); memcpy(v1, v2, sizeof (m_string_t)); // Note: nullify v2 to be safer - v2->ptr = NULL; + v2->u.buffer[sizeof(m_str1ng_heap_ct)-2] = 0; + v2->u.buffer[sizeof(m_str1ng_heap_ct)-1] = CHAR_MAX; M_STR1NG_CONTRACT (v1); } @@ -508,11 +578,10 @@ m_string_swap(m_string_t v1, m_string_t v2) { M_STR1NG_CONTRACT (v1); M_STR1NG_CONTRACT (v2); - // Even if it is stack based, we swap the heap representation - // which alias the stack based - M_SWAP (size_t, v1->u.heap.size, v2->u.heap.size); - M_SWAP (size_t, v1->u.heap.alloc, v2->u.heap.alloc); - M_SWAP (char *, v1->ptr, v2->ptr); + m_string_t tmp; + memcpy(tmp, v1, sizeof(m_string_t)); + memcpy(v1, v2, sizeof(m_string_t)); + memcpy(v2, tmp, sizeof(m_string_t)); M_STR1NG_CONTRACT (v1); M_STR1NG_CONTRACT (v2); } @@ -1389,15 +1458,15 @@ m_string_strim(m_string_t v, const char tab[]) M_INLINE bool m_string_oor_equal_p(const m_string_t s, unsigned char n) { - return (s->ptr == NULL) & (s->u.heap.alloc == ~(size_t)n); + return (s->u.buffer[sizeof (m_str1ng_heap_ct)-2] == 0) & (s->u.buffer[sizeof (m_str1ng_heap_ct)-1] == (char) ~n); } /* Set the uninitialized string to the OOR value */ M_INLINE void m_string_oor_set(m_string_t s, unsigned char n) { - s->ptr = NULL; - s->u.heap.alloc = ~(size_t)n; + s->u.buffer[sizeof (m_str1ng_heap_ct)-2] = 0; + s->u.buffer[sizeof (m_str1ng_heap_ct)-1] = (char) ~n; } /* I/O */ @@ -1917,15 +1986,14 @@ m_string_it_set_ref(m_string_it_t it, m_string_t s, m_string_unicode_t new_u) size_t old_u_size = (size_t) (str - it->ptr); // We need to replace old_u by new_u. Both are variable length size_t str_size = m_string_size(s); - m_str1ng_fit2size(s, str_size + new_u_size - old_u_size + 1); - ptr = m_str1ng_get_cstr(s); // ptr may be reallocated! if (new_u_size != old_u_size) { + ptr = m_str1ng_fit2size(s, str_size + new_u_size - old_u_size + 1); M_ASSUME( str_size+1 > (offset + old_u_size) ); memmove(&ptr[offset+new_u_size], &ptr[offset + old_u_size], str_size+1 - offset - old_u_size); + m_str1ng_set_size(s, str_size + new_u_size - old_u_size); } memcpy(&ptr[offset], &buffer[0], new_u_size); - m_str1ng_set_size(s, str_size + new_u_size - old_u_size); it->ptr = &ptr[offset]; M_STR1NG_IT_CONTRACT(it); M_STR1NG_CONTRACT(s); @@ -2055,8 +2123,9 @@ m_string_utf8_p(m_string_t str) #ifndef __cplusplus /* Initialize a constant string with the given C string */ # define M_STRING_CTE(s) \ - (m_string_srcptr)((const m_string_t){{.u.heap = { .size = sizeof(s)-1, .alloc = sizeof(s) } , \ - .ptr = ((struct { long long _n; char _d[sizeof (s)]; }){ 0, s })._d }}) + (m_string_srcptr)((const m_string_t){{.u.heap = { .size = sizeof(s)-1, \ + .alloc = { [sizeof(m_str1ng_size_t)-2] = 1, [sizeof(m_str1ng_size_t)-1] = 31}, \ + .ptr = ((struct { long long _n; char _d[sizeof (s)]; }){ 0, s })._d }}}) #else namespace m_lib { template @@ -2069,9 +2138,10 @@ namespace m_lib { inline m_aligned_string(const char lstr[]) { this->string->u.heap.size = N -1; - this->string->u.heap.alloc = N; + this->string->u.heap.alloc[sizeof(m_str1ng_size_t)-2] = 1; + this->string->u.heap.alloc[sizeof(m_str1ng_size_t)-1] = 31; memcpy (this->str, lstr, N); - this->string->ptr = this->str; + this->string->u.heap.ptr = this->str; } }; } @@ -2563,9 +2633,10 @@ namespace m_lib { /* Build dummy string to reuse m_string_get_str */ \ uintptr_t ptr = (uintptr_t) &s->s[0]; \ m_string_t v2; \ - v2->u.heap.size = strlen(s->s); \ - v2->u.heap.alloc = v2->u.heap.size + 1; \ - v2->ptr = (char*)ptr; \ + v2->u.heap.size = (m_str1ng_size_t) strlen(s->s); \ + v2->u.heap.alloc[sizeof(m_str1ng_size_t)-2] = 1; \ + v2->u.heap.alloc[sizeof(m_str1ng_size_t)-1] = 31; \ + v2->u.heap.ptr = (char*)ptr; \ m_string_get_str(v, v2, append); \ } \ \ @@ -2577,9 +2648,10 @@ namespace m_lib { /* Build dummy string to reuse m_string_get_str */ \ uintptr_t ptr = (uintptr_t) &s->s[0]; \ m_string_t v2; \ - v2->u.heap.size = strlen(s->s); \ - v2->u.heap.alloc = v2->u.heap.size + 1; \ - v2->ptr = (char*)ptr; \ + v2->u.heap.size = (m_str1ng_size_t) strlen(s->s); \ + v2->u.heap.alloc[sizeof(m_str1ng_size_t)-2] = 1; \ + v2->u.heap.alloc[sizeof(m_str1ng_size_t)-1] = 31; \ + v2->u.heap.ptr = (char*)ptr; \ m_string_out_str(f, v2); \ } \ \ diff --git a/tests/test-mstring.c b/tests/test-mstring.c index 136f05fd..d1125845 100644 --- a/tests/test-mstring.c +++ b/tests/test-mstring.c @@ -765,11 +765,36 @@ static void test0(void) string_init(s1); string_set_str(s1, "Hello"); - string_reserve(s1, 128); - assert(string_equal_str_p(s1, "Hello")); + for(i = 0 ; i < 2049; i++) { + string_reserve(s1, i); + assert(string_equal_str_p(s1, "Hello")); + } string_clear(s1); } +static void test_rounding(void) +{ + const size_t max = -(m_str1ng_size_t)1; + for(size_t a = max/2; a <= max; a++) { + unsigned char m, e; + size_t x = (size_t) a + a/2; + size_t nx = m_str1ng_round_capacity(&m, &e, (m_str1ng_size_t) x); + if (nx >= x) { + assert( x == (m_str1ng_size_t)x); + } + } + if (max != -(size_t)1) { + for(size_t a = max; a <= 2*max; a++) { + unsigned char m, e; + size_t x = (size_t) a + a/2; + size_t nx = m_str1ng_round_capacity(&m, &e, (m_str1ng_size_t) x); + if (nx >= x) { + assert( x == (m_str1ng_size_t)x); + } + } + } +} + static void test_int(void) { string_t s; @@ -1005,6 +1030,7 @@ static void test_parse_standard_c_type(void) int main(void) { test0(); + test_rounding(); test_M_LET(); test_parse_standard_c_type(); test_utf8_basic();