1.. _module-pw_string: 2 3========= 4pw_string 5========= 6String manipulation is a very common operation, but the standard C and C++ 7string libraries have drawbacks. The C++ functions are easy-to-use and powerful, 8but require too much flash and memory for many embedded projects. The C string 9functions are lighter weight, but can be difficult to use correctly. Mishandling 10of null terminators or buffer sizes can result in serious bugs. 11 12The ``pw_string`` module provides the flexibility, ease-of-use, and safety of 13C++-style string manipulation, but with no dynamic memory allocation and a much 14smaller binary size impact. Using ``pw_string`` in place of the standard C 15functions eliminates issues related to buffer overflow or missing null 16terminators. 17 18------------- 19Compatibility 20------------- 21C++17 22 23----- 24Usage 25----- 26pw::string::Format 27================== 28The ``pw::string::Format`` and ``pw::string::FormatVaList`` functions provide 29safer alternatives to ``std::snprintf`` and ``std::vsnprintf``. The snprintf 30return value is awkward to interpret, and misinterpreting it can lead to serious 31bugs. 32 33Size report: replacing snprintf with pw::string::Format 34------------------------------------------------------- 35The ``Format`` functions have a small, fixed code size cost. However, relative 36to equivalent ``std::snprintf`` calls, there is no incremental code size cost to 37using ``Format``. 38 39.. include:: format_size_report 40 41Safe Length Checking 42==================== 43This module provides two safer alternatives to ``std::strlen`` in case the 44string is extremely long and/or potentially not null-terminated. 45 46First, a constexpr alternative to C11's ``strnlen_s`` is offerred through 47:cpp:func:`pw::string::ClampedCString`. This does not return a length by 48design and instead returns a string_view which does not require 49null-termination. 50 51Second, a constexpr specialized form is offered where null termination is 52required through :cpp:func:`pw::string::NullTerminatedLength`. This will only 53return 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. 76 77pw::string::Copy 78================ 79The ``pw::string::Copy`` functions provide a safer alternative to 80``std::strncpy`` as it always null-terminates whenever the destination 81buffer 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 96pw::StringBuilder 97================= 98``pw::StringBuilder`` facilitates building formatted strings in a fixed-size 99buffer. It is designed to give the flexibility of ``std::string`` and 100``std::ostringstream``, but with a small footprint. 101 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 127Supporting custom types with StringBuilder 128------------------------------------------ 129As with ``std::ostream``, StringBuilder supports printing custom types by 130overriding the ``<<`` operator. This is is done by defining ``operator<<`` in 131the 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 148Internally, ``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. 151This 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) { 161 return Copy(MyStatusString(value), buffer); 162 } 163 164 } // namespace pw 165 166Size report: replacing snprintf with pw::StringBuilder 167------------------------------------------------------ 168StringBuilder is safe, flexible, and results in much smaller code size than 169using ``std::ostringstream``. However, applications sensitive to code size 170should use StringBuilder with care. 171 172The fixed code size cost of StringBuilder is significant, though smaller than 173``std::snprintf``. Using StringBuilder's << and append methods exclusively in 174place of ``snprintf`` reduces code size, but ``snprintf`` may be difficult to 175avoid. 176 177The incremental code size cost of StringBuilder is comparable to ``snprintf`` if 178errors are handled. Each argument to StringBuilder's ``<<`` expands to a 179function call, but one or two StringBuilder appends may have a smaller code size 180impact than a single ``snprintf`` call. 181 182.. include:: string_builder_size_report 183 184----------- 185Future work 186----------- 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. 190 191Zephyr 192====== 193To enable ``pw_string`` for Zephyr add ``CONFIG_PIGWEED_STRING=y`` to the 194project's configuration. 195