• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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