Wyatt Hepler | f9fb90f | 2020-09-30 18:59:33 -0700 | [diff] [blame] | 1 | .. _module-pw_string: |
Wyatt Hepler | fe85de2 | 2019-11-19 17:10:20 -0800 | [diff] [blame] | 2 | |
Armando Montanez | 1cbc49a | 2021-11-19 18:30:27 -0800 | [diff] [blame] | 3 | ========= |
Wyatt Hepler | fe85de2 | 2019-11-19 17:10:20 -0800 | [diff] [blame] | 4 | pw_string |
Armando Montanez | 1cbc49a | 2021-11-19 18:30:27 -0800 | [diff] [blame] | 5 | ========= |
Wyatt Hepler | 3c4e5de | 2020-03-03 14:37:52 -0800 | [diff] [blame] | 6 | String manipulation is a very common operation, but the standard C and C++ |
| 7 | string libraries have drawbacks. The C++ functions are easy-to-use and powerful, |
| 8 | but require too much flash and memory for many embedded projects. The C string |
| 9 | functions are lighter weight, but can be difficult to use correctly. Mishandling |
| 10 | of null terminators or buffer sizes can result in serious bugs. |
| 11 | |
Armando Montanez | 0054a9b | 2020-03-13 13:06:24 -0700 | [diff] [blame] | 12 | The ``pw_string`` module provides the flexibility, ease-of-use, and safety of |
Wyatt Hepler | 3c4e5de | 2020-03-03 14:37:52 -0800 | [diff] [blame] | 13 | C++-style string manipulation, but with no dynamic memory allocation and a much |
Armando Montanez | 0054a9b | 2020-03-13 13:06:24 -0700 | [diff] [blame] | 14 | smaller binary size impact. Using ``pw_string`` in place of the standard C |
| 15 | functions eliminates issues related to buffer overflow or missing null |
| 16 | terminators. |
Wyatt Hepler | fe85de2 | 2019-11-19 17:10:20 -0800 | [diff] [blame] | 17 | |
Armando Montanez | 1cbc49a | 2021-11-19 18:30:27 -0800 | [diff] [blame] | 18 | ------------- |
Wyatt Hepler | ee3e02f | 2019-12-05 10:52:31 -0800 | [diff] [blame] | 19 | Compatibility |
Armando Montanez | 1cbc49a | 2021-11-19 18:30:27 -0800 | [diff] [blame] | 20 | ------------- |
Wyatt Hepler | fe85de2 | 2019-11-19 17:10:20 -0800 | [diff] [blame] | 21 | C++17 |
| 22 | |
Armando Montanez | 1cbc49a | 2021-11-19 18:30:27 -0800 | [diff] [blame] | 23 | ----- |
| 24 | Usage |
| 25 | ----- |
Wyatt Hepler | fe85de2 | 2019-11-19 17:10:20 -0800 | [diff] [blame] | 26 | pw::string::Format |
Wyatt Hepler | 0435efe | 2021-03-01 14:00:36 -0800 | [diff] [blame] | 27 | ================== |
Wyatt Hepler | 2596fe5 | 2020-01-23 17:40:10 -0800 | [diff] [blame] | 28 | The ``pw::string::Format`` and ``pw::string::FormatVaList`` functions provide |
| 29 | safer alternatives to ``std::snprintf`` and ``std::vsnprintf``. The snprintf |
| 30 | return value is awkward to interpret, and misinterpreting it can lead to serious |
| 31 | bugs. |
Wyatt Hepler | fe85de2 | 2019-11-19 17:10:20 -0800 | [diff] [blame] | 32 | |
| 33 | Size report: replacing snprintf with pw::string::Format |
Wyatt Hepler | 0435efe | 2021-03-01 14:00:36 -0800 | [diff] [blame] | 34 | ------------------------------------------------------- |
Wyatt Hepler | fe85de2 | 2019-11-19 17:10:20 -0800 | [diff] [blame] | 35 | The ``Format`` functions have a small, fixed code size cost. However, relative |
| 36 | to equivalent ``std::snprintf`` calls, there is no incremental code size cost to |
| 37 | using ``Format``. |
| 38 | |
Alexei Frolov | 725b85b | 2020-03-19 13:37:10 -0700 | [diff] [blame] | 39 | .. include:: format_size_report |
Wyatt Hepler | fe85de2 | 2019-11-19 17:10:20 -0800 | [diff] [blame] | 40 | |
Ewout van Bekkum | f89f137 | 2021-05-03 11:15:54 -0700 | [diff] [blame] | 41 | Safe Length Checking |
| 42 | ==================== |
| 43 | This module provides two safer alternatives to ``std::strlen`` in case the |
| 44 | string is extremely long and/or potentially not null-terminated. |
Ewout van Bekkum | c2e9d88 | 2021-04-29 16:01:27 -0700 | [diff] [blame] | 45 | |
Ewout van Bekkum | f89f137 | 2021-05-03 11:15:54 -0700 | [diff] [blame] | 46 | First, a constexpr alternative to C11's ``strnlen_s`` is offerred through |
| 47 | :cpp:func:`pw::string::ClampedCString`. This does not return a length by |
| 48 | design and instead returns a string_view which does not require |
| 49 | null-termination. |
Ewout van Bekkum | c2e9d88 | 2021-04-29 16:01:27 -0700 | [diff] [blame] | 50 | |
Ewout van Bekkum | f89f137 | 2021-05-03 11:15:54 -0700 | [diff] [blame] | 51 | Second, a constexpr specialized form is offered where null termination is |
| 52 | required through :cpp:func:`pw::string::NullTerminatedLength`. This will only |
| 53 | return a length if the string is null-terminated. |
| 54 | |
| 55 | .. cpp:function:: constexpr std::string_view pw::string::ClampedCString(std::span<const char> str) |
| 56 | .. cpp:function:: constexpr std::string_view pw::string::ClampedCString(const char* str, size_t max_len) |
| 57 | |
| 58 | Safe alternative to the string_view constructor to avoid the risk of an |
| 59 | unbounded implicit or explicit use of strlen. |
| 60 | |
| 61 | This is strongly recommended over using something like C11's strnlen_s as |
| 62 | a string_view does not require null-termination. |
| 63 | |
| 64 | .. cpp:function:: constexpr pw::Result<size_t> pw::string::NullTerminatedLength(std::span<const char> str) |
| 65 | .. cpp:function:: pw::Result<size_t> pw::string::NullTerminatedLength(const char* str, size_t max_len) |
| 66 | |
| 67 | Safe alternative to strlen to calculate the null-terminated length of the |
| 68 | string within the specified span, excluding the null terminator. Like C11's |
| 69 | strnlen_s, the scan for the null-terminator is bounded. |
| 70 | |
| 71 | Returns: |
| 72 | null-terminated length of the string excluding the null terminator. |
| 73 | OutOfRange - if the string is not null-terminated. |
| 74 | |
| 75 | Precondition: The string shall be at a valid pointer. |
Ewout van Bekkum | c2e9d88 | 2021-04-29 16:01:27 -0700 | [diff] [blame] | 76 | |
| 77 | pw::string::Copy |
| 78 | ================ |
| 79 | The ``pw::string::Copy`` functions provide a safer alternative to |
| 80 | ``std::strncpy`` as it always null-terminates whenever the destination |
| 81 | buffer has a non-zero size. |
| 82 | |
| 83 | .. cpp:function:: StatusWithSize Copy(const std::string_view& source, std::span<char> dest) |
| 84 | .. cpp:function:: StatusWithSize Copy(const char* source, std::span<char> dest) |
| 85 | .. cpp:function:: StatusWithSize Copy(const char* source, char* dest, size_t num) |
| 86 | |
| 87 | Copies the source string to the dest, truncating if the full string does not |
| 88 | fit. Always null terminates if dest.size() or num > 0. |
| 89 | |
| 90 | Returns the number of characters written, excluding the null terminator. If |
| 91 | the string is truncated, the status is ResourceExhausted. |
| 92 | |
| 93 | Precondition: The destination and source shall not overlap. |
| 94 | Precondition: The source shall be a valid pointer. |
| 95 | |
Wyatt Hepler | fe85de2 | 2019-11-19 17:10:20 -0800 | [diff] [blame] | 96 | pw::StringBuilder |
Wyatt Hepler | 0435efe | 2021-03-01 14:00:36 -0800 | [diff] [blame] | 97 | ================= |
| 98 | ``pw::StringBuilder`` facilitates building formatted strings in a fixed-size |
| 99 | buffer. It is designed to give the flexibility of ``std::string`` and |
| 100 | ``std::ostringstream``, but with a small footprint. |
| 101 | |
Armando Montanez | 1cbc49a | 2021-11-19 18:30:27 -0800 | [diff] [blame] | 102 | .. code-block:: cpp |
| 103 | |
| 104 | #include "pw_log/log.h" |
| 105 | #include "pw_string/string_builder.h" |
| 106 | |
| 107 | pw::Status LogProducedData(std::string_view func_name, |
| 108 | std::span<const std::byte> data) { |
| 109 | pw::StringBuffer<42> sb; |
| 110 | |
| 111 | // Append a std::string_view to the buffer. |
| 112 | sb << func_name; |
| 113 | |
| 114 | // Append a format string to the buffer. |
| 115 | sb.Format(" produced %d bytes of data: ", static_cast<int>(data.data())); |
| 116 | |
| 117 | // Append bytes as hex to the buffer. |
| 118 | sb << data; |
| 119 | |
| 120 | // Log the final string. |
| 121 | PW_LOG_DEBUG("%s", sb.c_str()); |
| 122 | |
| 123 | // Errors encountered while mutating the string builder are tracked. |
| 124 | return sb.status(); |
| 125 | } |
| 126 | |
Wyatt Hepler | 0435efe | 2021-03-01 14:00:36 -0800 | [diff] [blame] | 127 | Supporting custom types with StringBuilder |
| 128 | ------------------------------------------ |
| 129 | As with ``std::ostream``, StringBuilder supports printing custom types by |
| 130 | overriding the ``<<`` operator. This is is done by defining ``operator<<`` in |
| 131 | the same namespace as the custom type. For example: |
| 132 | |
| 133 | .. code-block:: cpp |
| 134 | |
| 135 | namespace my_project { |
| 136 | |
| 137 | struct MyType { |
| 138 | int foo; |
| 139 | const char* bar; |
| 140 | }; |
| 141 | |
| 142 | pw::StringBuilder& operator<<(pw::StringBuilder& sb, const MyType& value) { |
| 143 | return sb << "MyType(" << value.foo << ", " << value.bar << ')'; |
| 144 | } |
| 145 | |
| 146 | } // namespace my_project |
| 147 | |
| 148 | Internally, ``StringBuilder`` uses the ``ToString`` function to print. The |
| 149 | ``ToString`` template function can be specialized to support custom types with |
| 150 | ``StringBuilder``, though it is recommended to overload ``operator<<`` instead. |
| 151 | This example shows how to specialize ``pw::ToString``: |
| 152 | |
| 153 | .. code-block:: cpp |
| 154 | |
| 155 | #include "pw_string/to_string.h" |
| 156 | |
| 157 | namespace pw { |
| 158 | |
| 159 | template <> |
| 160 | StatusWithSize ToString<MyStatus>(MyStatus value, std::span<char> buffer) { |
Ewout van Bekkum | c2e9d88 | 2021-04-29 16:01:27 -0700 | [diff] [blame] | 161 | return Copy(MyStatusString(value), buffer); |
Wyatt Hepler | 0435efe | 2021-03-01 14:00:36 -0800 | [diff] [blame] | 162 | } |
| 163 | |
| 164 | } // namespace pw |
Wyatt Hepler | fe85de2 | 2019-11-19 17:10:20 -0800 | [diff] [blame] | 165 | |
| 166 | Size report: replacing snprintf with pw::StringBuilder |
Wyatt Hepler | 0435efe | 2021-03-01 14:00:36 -0800 | [diff] [blame] | 167 | ------------------------------------------------------ |
| 168 | StringBuilder is safe, flexible, and results in much smaller code size than |
| 169 | using ``std::ostringstream``. However, applications sensitive to code size |
| 170 | should use StringBuilder with care. |
| 171 | |
Wyatt Hepler | fe85de2 | 2019-11-19 17:10:20 -0800 | [diff] [blame] | 172 | The fixed code size cost of StringBuilder is significant, though smaller than |
Armando Montanez | 0054a9b | 2020-03-13 13:06:24 -0700 | [diff] [blame] | 173 | ``std::snprintf``. Using StringBuilder's << and append methods exclusively in |
| 174 | place of ``snprintf`` reduces code size, but ``snprintf`` may be difficult to |
| 175 | avoid. |
Wyatt Hepler | fe85de2 | 2019-11-19 17:10:20 -0800 | [diff] [blame] | 176 | |
Armando Montanez | 0054a9b | 2020-03-13 13:06:24 -0700 | [diff] [blame] | 177 | The incremental code size cost of StringBuilder is comparable to ``snprintf`` if |
Wyatt Hepler | 0435efe | 2021-03-01 14:00:36 -0800 | [diff] [blame] | 178 | errors are handled. Each argument to StringBuilder's ``<<`` expands to a |
| 179 | function call, but one or two StringBuilder appends may have a smaller code size |
| 180 | impact than a single ``snprintf`` call. |
Wyatt Hepler | fe85de2 | 2019-11-19 17:10:20 -0800 | [diff] [blame] | 181 | |
Alexei Frolov | 725b85b | 2020-03-19 13:37:10 -0700 | [diff] [blame] | 182 | .. include:: string_builder_size_report |
Wyatt Hepler | fe85de2 | 2019-11-19 17:10:20 -0800 | [diff] [blame] | 183 | |
Armando Montanez | 1cbc49a | 2021-11-19 18:30:27 -0800 | [diff] [blame] | 184 | ----------- |
Wyatt Hepler | fe85de2 | 2019-11-19 17:10:20 -0800 | [diff] [blame] | 185 | Future work |
Armando Montanez | 1cbc49a | 2021-11-19 18:30:27 -0800 | [diff] [blame] | 186 | ----------- |
Wyatt Hepler | fe85de2 | 2019-11-19 17:10:20 -0800 | [diff] [blame] | 187 | * StringBuilder's fixed size cost can be dramatically reduced by limiting |
| 188 | support for 64-bit integers. |
| 189 | * Consider integrating with the tokenizer module. |
Yuval Peress | b8f3ad2 | 2021-10-26 22:55:27 -0600 | [diff] [blame] | 190 | |
| 191 | Zephyr |
| 192 | ====== |
| 193 | To enable ``pw_string`` for Zephyr add ``CONFIG_PIGWEED_STRING=y`` to the |
| 194 | project's configuration. |