| .. _module-pw_string: |
| |
| --------- |
| pw_string |
| --------- |
| String manipulation is a very common operation, but the standard C and C++ |
| string libraries have drawbacks. The C++ functions are easy-to-use and powerful, |
| but require too much flash and memory for many embedded projects. The C string |
| functions are lighter weight, but can be difficult to use correctly. Mishandling |
| of null terminators or buffer sizes can result in serious bugs. |
| |
| The ``pw_string`` module provides the flexibility, ease-of-use, and safety of |
| C++-style string manipulation, but with no dynamic memory allocation and a much |
| smaller binary size impact. Using ``pw_string`` in place of the standard C |
| functions eliminates issues related to buffer overflow or missing null |
| terminators. |
| |
| Compatibility |
| ============= |
| C++17 |
| |
| pw::string::Format |
| ================== |
| The ``pw::string::Format`` and ``pw::string::FormatVaList`` functions provide |
| safer alternatives to ``std::snprintf`` and ``std::vsnprintf``. The snprintf |
| return value is awkward to interpret, and misinterpreting it can lead to serious |
| bugs. |
| |
| Size report: replacing snprintf with pw::string::Format |
| ------------------------------------------------------- |
| The ``Format`` functions have a small, fixed code size cost. However, relative |
| to equivalent ``std::snprintf`` calls, there is no incremental code size cost to |
| using ``Format``. |
| |
| .. include:: format_size_report |
| |
| Safe Length Checking |
| ==================== |
| This module provides two safer alternatives to ``std::strlen`` in case the |
| string is extremely long and/or potentially not null-terminated. |
| |
| First, a constexpr alternative to C11's ``strnlen_s`` is offerred through |
| :cpp:func:`pw::string::ClampedCString`. This does not return a length by |
| design and instead returns a string_view which does not require |
| null-termination. |
| |
| Second, a constexpr specialized form is offered where null termination is |
| required through :cpp:func:`pw::string::NullTerminatedLength`. This will only |
| return a length if the string is null-terminated. |
| |
| .. cpp:function:: constexpr std::string_view pw::string::ClampedCString(std::span<const char> str) |
| .. cpp:function:: constexpr std::string_view pw::string::ClampedCString(const char* str, size_t max_len) |
| |
| Safe alternative to the string_view constructor to avoid the risk of an |
| unbounded implicit or explicit use of strlen. |
| |
| This is strongly recommended over using something like C11's strnlen_s as |
| a string_view does not require null-termination. |
| |
| .. cpp:function:: constexpr pw::Result<size_t> pw::string::NullTerminatedLength(std::span<const char> str) |
| .. cpp:function:: pw::Result<size_t> pw::string::NullTerminatedLength(const char* str, size_t max_len) |
| |
| Safe alternative to strlen to calculate the null-terminated length of the |
| string within the specified span, excluding the null terminator. Like C11's |
| strnlen_s, the scan for the null-terminator is bounded. |
| |
| Returns: |
| null-terminated length of the string excluding the null terminator. |
| OutOfRange - if the string is not null-terminated. |
| |
| Precondition: The string shall be at a valid pointer. |
| |
| pw::string::Copy |
| ================ |
| The ``pw::string::Copy`` functions provide a safer alternative to |
| ``std::strncpy`` as it always null-terminates whenever the destination |
| buffer has a non-zero size. |
| |
| .. cpp:function:: StatusWithSize Copy(const std::string_view& source, std::span<char> dest) |
| .. cpp:function:: StatusWithSize Copy(const char* source, std::span<char> dest) |
| .. cpp:function:: StatusWithSize Copy(const char* source, char* dest, size_t num) |
| |
| Copies the source string to the dest, truncating if the full string does not |
| fit. Always null terminates if dest.size() or num > 0. |
| |
| Returns the number of characters written, excluding the null terminator. If |
| the string is truncated, the status is ResourceExhausted. |
| |
| Precondition: The destination and source shall not overlap. |
| Precondition: The source shall be a valid pointer. |
| |
| pw::StringBuilder |
| ================= |
| ``pw::StringBuilder`` facilitates building formatted strings in a fixed-size |
| buffer. It is designed to give the flexibility of ``std::string`` and |
| ``std::ostringstream``, but with a small footprint. |
| |
| Supporting custom types with StringBuilder |
| ------------------------------------------ |
| As with ``std::ostream``, StringBuilder supports printing custom types by |
| overriding the ``<<`` operator. This is is done by defining ``operator<<`` in |
| the same namespace as the custom type. For example: |
| |
| .. code-block:: cpp |
| |
| namespace my_project { |
| |
| struct MyType { |
| int foo; |
| const char* bar; |
| }; |
| |
| pw::StringBuilder& operator<<(pw::StringBuilder& sb, const MyType& value) { |
| return sb << "MyType(" << value.foo << ", " << value.bar << ')'; |
| } |
| |
| } // namespace my_project |
| |
| Internally, ``StringBuilder`` uses the ``ToString`` function to print. The |
| ``ToString`` template function can be specialized to support custom types with |
| ``StringBuilder``, though it is recommended to overload ``operator<<`` instead. |
| This example shows how to specialize ``pw::ToString``: |
| |
| .. code-block:: cpp |
| |
| #include "pw_string/to_string.h" |
| |
| namespace pw { |
| |
| template <> |
| StatusWithSize ToString<MyStatus>(MyStatus value, std::span<char> buffer) { |
| return Copy(MyStatusString(value), buffer); |
| } |
| |
| } // namespace pw |
| |
| Size report: replacing snprintf with pw::StringBuilder |
| ------------------------------------------------------ |
| StringBuilder is safe, flexible, and results in much smaller code size than |
| using ``std::ostringstream``. However, applications sensitive to code size |
| should use StringBuilder with care. |
| |
| The fixed code size cost of StringBuilder is significant, though smaller than |
| ``std::snprintf``. Using StringBuilder's << and append methods exclusively in |
| place of ``snprintf`` reduces code size, but ``snprintf`` may be difficult to |
| avoid. |
| |
| The incremental code size cost of StringBuilder is comparable to ``snprintf`` if |
| errors are handled. Each argument to StringBuilder's ``<<`` expands to a |
| function call, but one or two StringBuilder appends may have a smaller code size |
| impact than a single ``snprintf`` call. |
| |
| .. include:: string_builder_size_report |
| |
| Future work |
| =========== |
| * StringBuilder's fixed size cost can be dramatically reduced by limiting |
| support for 64-bit integers. |
| * Consider integrating with the tokenizer module. |