From cb6d924dad024bdc2c4db6da06a5b38f29f9d04b Mon Sep 17 00:00:00 2001 From: John Wellbelove Date: Thu, 29 May 2025 20:15:19 +0100 Subject: [PATCH] Optimisation of strings --- include/etl/basic_string.h | 178 ++++++++++++++++++++++++--------- include/etl/string_utilities.h | 44 ++++++++ test/test_string_utilities.cpp | 79 +++++++++++++++ 3 files changed, 251 insertions(+), 50 deletions(-) diff --git a/include/etl/basic_string.h b/include/etl/basic_string.h index 0b0404b5..d4f1371c 100644 --- a/include/etl/basic_string.h +++ b/include/etl/basic_string.h @@ -45,6 +45,7 @@ SOFTWARE. #include "memory.h" #include "binary.h" #include "flags.h" +#include "string_utilities.h" #include #include @@ -247,11 +248,9 @@ namespace etl //************************************************************************* /// Returns whether the string was truncated by the last operation. - /// Deprecated. Use is_truncated() ///\return Whether the string was truncated by the last operation. //************************************************************************* - ETL_DEPRECATED - bool truncated() const + bool is_truncated() const { #if ETL_HAS_STRING_TRUNCATION_CHECKS return flags.test(); @@ -262,15 +261,13 @@ namespace etl //************************************************************************* /// Returns whether the string was truncated by the last operation. + /// Deprecated. Use is_truncated() ///\return Whether the string was truncated by the last operation. //************************************************************************* - bool is_truncated() const + ETL_DEPRECATED + bool truncated() const { -#if ETL_HAS_STRING_TRUNCATION_CHECKS - return flags.test(); -#else - return false; -#endif + return is_truncated(); } #if ETL_HAS_STRING_TRUNCATION_CHECKS @@ -724,7 +721,7 @@ namespace etl //********************************************************************* void assign(const_pointer text) { - assign_impl(text, text + etl::strlen(text), false, false); + assign_impl(text, false, false); } //********************************************************************* @@ -761,7 +758,7 @@ namespace etl set_truncated(n > CAPACITY); #if ETL_HAS_ERROR_ON_STRING_TRUNCATION - ETL_ASSERT(flags.test() == false, ETL_ERROR(string_truncation)); + ETL_ASSERT(is_truncated == false, ETL_ERROR(string_truncation)); #endif #endif @@ -1093,10 +1090,7 @@ namespace etl current_size = CAPACITY; - while (position_ != end()) - { - *position_++ = *first++; - } + position_ = copy_characters(first, etl::distance(position_, end()), position_); } else { @@ -1127,10 +1121,7 @@ namespace etl etl::copy_backward(position_, position_ + characters_to_shift, begin() + to_position + characters_to_shift); - while (first != last) - { - *position_++ = *first++; - } + position_ = copy_characters(first, etl::distance(first, last), position_); } p_buffer[current_size] = 0; @@ -1310,7 +1301,7 @@ namespace etl //********************************************************************* iterator erase(iterator i_element) { - etl::copy(i_element + 1, end(), i_element); + etl::mem_copy(i_element + 1, end(), i_element); p_buffer[--current_size] = 0; return i_element; @@ -1325,7 +1316,7 @@ namespace etl { iterator i_element_(to_iterator(i_element)); - etl::copy(i_element_ + 1, end(), i_element_); + etl::mem_copy(i_element + 1, end(), i_element_); p_buffer[--current_size] = 0; return i_element_; @@ -1349,7 +1340,7 @@ namespace etl return first_; } - etl::copy(last_, end(), first_); + etl::mem_copy(last_, end(), first_); size_type n_delete = etl::distance(first_, last_); current_size -= n_delete; @@ -1386,7 +1377,7 @@ namespace etl count = size() - pos; } - etl::copy_n(p_buffer + pos, count, dest); + etl::mem_copy(p_buffer + pos, count, dest); return count; } @@ -2559,16 +2550,24 @@ namespace etl //************************************************************************* /// Compare helper function //************************************************************************* - int compare(const_pointer first1, const_pointer last1, const_pointer first2, const_pointer last2) const + static int compare(const_pointer first1, const_pointer last1, + const_pointer first2, const_pointer last2) { - while ((first1 != last1) && (first2 != last2)) + typedef typename etl::make_unsigned::type type; + + difference_type length1 = etl::distance(first1, last1); + difference_type length2 = etl::distance(first2, last2); + difference_type compare_length = min(length1, length2); + + // First compare the string characters. + while (compare_length != 0) { - if (*first1 < *first2) + if (static_cast(*first1) < static_cast(*first2)) { // Compared character is lower. return -1; } - else if (*first1 > *first2) + else if (static_cast(*first1) > static_cast(*first2)) { // Compared character is higher. return 1; @@ -2576,24 +2575,24 @@ namespace etl ++first1; ++first2; + --compare_length; } - // We reached the end of one or both of the strings. - if ((first1 == last1) && (first2 == last2)) + // Then compare the string lengths. + if (length1 < length2) { - // Same length. - return 0; - } - else if (first1 == last1) - { - // Compared string is shorter. + // First string is shorter. return -1; } - else + + if (length1 > length2) { - // Compared string is longer. + // First string is longer. return 1; } + + // Strings are the same length. + return 0; } //************************************************************************* @@ -2651,30 +2650,91 @@ namespace etl private: //********************************************************************* - /// Common implementation for 'assign'. + /// Copy characters using pointers. + /// Returns a pointer to the character after the last copied. + //********************************************************************* + template + typename etl::enable_if::value && etl::is_pointer::value, TIterator2>::type + copy_characters(TIterator1 from, size_t dist, TIterator2 to) + { + etl::mem_copy(from, dist, to); + + return to + dist; + } + + //********************************************************************* + /// Copy characters using non-pointers. + /// Returns an iterator to the character after the last copied. + //********************************************************************* + template + typename etl::enable_if::value || !etl::is_pointer::value, TIterator2>::type + copy_characters(TIterator1 from, size_t dist, TIterator2 to) + { + size_t count = 0; + + while (count != dist) + { + *to++ = *from++; + ++count; + } + + return to; + } + + //********************************************************************* + /// Common implementation for 'assign' for iterators. //********************************************************************* template void assign_impl(TIterator first, TIterator last, bool truncated, bool secure) { -#if ETL_IS_DEBUG_BUILD - difference_type d = etl::distance(first, last); - ETL_ASSERT(d >= 0, ETL_ERROR(string_iterator)); -#endif - initialise(); - while ((first != last) && (current_size != CAPACITY)) - { - p_buffer[current_size++] = *first++; - } + difference_type dist = etl::distance(first, last); +#if ETL_IS_DEBUG_BUILD + ETL_ASSERT(dist >= 0, ETL_ERROR(string_iterator)); +#endif + +#if ETL_HAS_STRING_TRUNCATION_CHECKS + set_truncated((size_t(dist) > CAPACITY) || truncated); + +#if ETL_HAS_ERROR_ON_STRING_TRUNCATION + ETL_ASSERT(is_truncated == false, ETL_ERROR(string_truncation)); +#endif +#endif + +#if ETL_HAS_STRING_CLEAR_AFTER_USE + if (secure) + { + set_secure(); + } +#endif + + // Limit the actual distance to the capacity. + dist = difference_type(min(size_t(dist), CAPACITY)); + copy_characters(first, dist, p_buffer); + current_size = dist; + p_buffer[current_size] = 0; + + cleanup(); + } + + //********************************************************************* + /// Common implementation for 'assign' for single pointer. + //********************************************************************* + void assign_impl(const_pointer first, bool truncated, bool secure) + { + initialise(); + + etl::str_n_copy_result result = etl::str_n_copy(first, CAPACITY, p_buffer); + + current_size = result.count; p_buffer[current_size] = 0; #if ETL_HAS_STRING_TRUNCATION_CHECKS - set_truncated((first != last) || truncated); - + set_truncated(result.truncated || truncated); #if ETL_HAS_ERROR_ON_STRING_TRUNCATION - ETL_ASSERT(flags.test() == false, ETL_ERROR(string_truncation)); + ETL_ASSERT(is_truncated == false, ETL_ERROR(string_truncation)); #endif #endif @@ -2979,6 +3039,24 @@ namespace etl return !(lhs < rhs); } + //*************************************************************************** + /// Spaceship operator. + ///\param lhs Reference to the first string. + ///\param rhs Reference to the second string. + ///\return -1 if lhs < rhs + /// 0 if lhs == rhs + /// 1 if lhs > rhs + ///\ingroup string + //*************************************************************************** +#if ETL_USING_CPP20 + template + int operator <=>(const etl::ibasic_string& lhs, + const etl::ibasic_string& rhs) + { + return lhs.compare(rhs); + } +#endif + //*************************************************************************** /// Operator overload to write to std basic_ostream ///\param os Reference to the output stream. diff --git a/include/etl/string_utilities.h b/include/etl/string_utilities.h index 46ead0bf..626cd11d 100644 --- a/include/etl/string_utilities.h +++ b/include/etl/string_utilities.h @@ -883,6 +883,50 @@ namespace etl etl::transform(itr, s.end(), itr, ::tolower); } + + //*************************************************************************** + /// str_n_copy + /// Copies from src to dst until either n characters have been copied, or a + /// terminating null is found. + /// Null terminates the destination if less than n characters have been copied. + /// Returns a str_n_copy_result. + //*************************************************************************** + struct str_n_copy_result + { + size_t count; + bool truncated; + bool terminated; + }; + + template + str_n_copy_result str_n_copy(const T* src, size_t n, T* dst) + { + if ((src == ETL_NULLPTR) || (dst == ETL_NULLPTR)) + { + return str_n_copy_result{ 0, false, false }; + } + + size_t count = 0; + + while ((count != n) && (*src != 0)) + { + *dst++ = *src++; + ++count; + } + + // Did we stop because of a terminating zero? + if (count != n) + { + // Yes we did. + *dst = 0; + return str_n_copy_result{ count, false, true }; + } + else + { + // No. Truncation depends on the next src character being a terminating zero or not. + return str_n_copy_result{ count, *src != 0, false }; + } + } } #include "private/minmax_pop.h" diff --git a/test/test_string_utilities.cpp b/test/test_string_utilities.cpp index 2b661322..d05c9334 100644 --- a/test/test_string_utilities.cpp +++ b/test/test_string_utilities.cpp @@ -44,6 +44,9 @@ SOFTWARE. #undef STR_PTR #define STR_PTR const char* +#undef STR_TYPE +#define STR_TYPE char + namespace { SUITE(test_string_utilities_char) @@ -1781,5 +1784,81 @@ namespace CHECK(text == expected); } + + //************************************************************************* + TEST(test_str_n_copy_destination_larger) + { + STR_PTR src = STR("Hello World"); + STR_TYPE dst[15]; + + etl::str_n_copy_result result = etl::str_n_copy(src, 15, dst); + CHECK_EQUAL(etl::strlen(src), result.count); + CHECK_FALSE(result.truncated); + CHECK_TRUE(result.terminated); + CHECK(dst[11] == 0); + + bool are_equal = etl::equal(src, src + 12, dst); + CHECK(are_equal); + } + + //************************************************************************* + TEST(test_str_n_copy_destination_exact_include_space_for_terminator) + { + STR_PTR src = STR("Hello World"); + STR_TYPE dst[12]; + + etl::str_n_copy_result result = etl::str_n_copy(src, 12, dst); + CHECK_EQUAL(11, result.count); + CHECK_FALSE(result.truncated); + CHECK_TRUE(result.terminated); + CHECK(dst[11] == 0); + + bool are_equal = etl::equal(src, src + 12, dst); + CHECK(are_equal); + } + + //************************************************************************* + TEST(test_str_n_copy_destination_exclude_space_for_terminator_exact_no_termination) + { + STR_PTR src = STR("Hello World"); + STR_TYPE dst[11]; + + etl::str_n_copy_result result = etl::str_n_copy(src, 11, dst); + CHECK_EQUAL(11, result.count); + CHECK_FALSE(result.truncated); + CHECK_FALSE(result.terminated); + CHECK(dst[10] != 0); + + bool are_equal = etl::equal(src, src + 11, dst); + CHECK(are_equal); + } + + //************************************************************************* + TEST(test_str_n_copy_destination_smaller_no_termination) + { + STR_PTR src = STR("Hello World"); + STR_TYPE dst[9]; + + etl::str_n_copy_result result = etl::str_n_copy(src, 9, dst); + CHECK_EQUAL(9, result.count); + CHECK_TRUE(result.truncated); + CHECK_FALSE(result.terminated); + CHECK(dst[8] != 0); + + bool are_equal = etl::equal(src, src + 9, dst); + CHECK(are_equal); + } + + //************************************************************************* + TEST(test_str_n_copy_zero_sized_destination) + { + STR_PTR src = STR("Hello World"); + STR_TYPE dst[15]; + + etl::str_n_copy_result result = etl::str_n_copy(src, 0, dst); + CHECK_EQUAL(0, result.count); + CHECK_TRUE(result.truncated); + CHECK_FALSE(result.terminated); + } }; }