pw_string#

Efficient, easy, and safe string manipulation

  • Efficient: No memory allocation, no pointer indirection.

  • Easy: Use the string API you already know.

  • Safe: Never worry about buffer overruns or undefined behavior.

Pick three! If you know how to use std::string, just use pw::InlineString in the same way:

// Create a string from a C-style char array; storage is pre-allocated!
pw::InlineString<16> my_string = "Literally";

// We have some space left, so let's add to the string.
my_string.append('?', 3);  // "Literally???"

// Let's try something evil and extend this past its capacity 😈
my_string.append('!', 8);
// Foiled by a crash! No mysterious bugs or undefined behavior.

Need to build up a string? pw::StringBuilder works like std::ostringstream, but with most of the efficiency and memory benefits of pw::InlineString:

// Create a pw::StringBuilder with a built-in buffer
pw::StringBuffer<32> my_string_builder = "Is it really this easy?";

// Add to it with idiomatic C++
my_string << " YES!";

// Use it like any other string
PW_LOG_DEBUG("%s", my_string_builder.c_str());

Check out pw_string: Guide for more code samples.

Stable C++14 C++17 Code Size Impact: 500 to 1500 bytes

Background#

String manipulation on embedded systems can be surprisingly challenging. C strings are light weight but come with many pitfalls for those who don’t know the standard library deeply. C++ provides string classes that are safe and easy to use, but they consume way too much code space and are designed to be used with dynamic memory allocation.

Embedded systems need string functionality that is both safe and suitable for resource-constrained platforms.

Our solution#

pw_string provides safe string handling functionality with an API that closely matches that of std::string, but without dynamic memory allocation and with a much smaller binary size impact.

Who this is for#

pw_string is useful any time you need to handle strings in embedded C++.

Is it right for you?#

If your project written in C, pw_string is not a good fit since we don’t currently expose a C API.

For larger platforms where code space isn’t in short supply and dynamic memory allocation isn’t a problem, you may find that std::string meets your needs.

Tip

pw_string works just as well on larger embedded platforms and host systems. Using pw_string even when you might get away with std:string gives you the flexibility to move to smaller platforms later with much less rework.

Here are some size reports that may affect whether pw_string is right for you.

Size comparison: snprintf versus pw::StringBuilder#

pw::StringBuilder is safe, flexible, and results in much smaller code size than using std::ostringstream. However, applications sensitive to code size should use pw::StringBuilder with care.

The fixed code size cost of pw::StringBuilder is significant, though smaller than std::snprintf. Using pw::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 pw::StringBuilder is comparable to snprintf if errors are handled. Each argument to pw::StringBuilder’s << method expands to a function call, but one or two pw::StringBuilder appends may have a smaller code size impact than a single snprintf call.

Label

Segment

Delta

Total StringBuilder cost when used alongside snprintf

FLASH

+48

[section .code]

-4

quorem

-2

_ctype_

-2

__aeabi_d2f

+92

main

+4

CSWTCH.1

+8

__sf_fake_stdout

NEW

+720

__udivmoddi4

NEW

+276

pw::string::IntToString<>()

NEW

+160

pw::string::(anonymous namespace)::kPowersOf10

NEW

+84

pw::StringBuilder::append()

NEW

+64

pw::string::DecimalDigitCount()

NEW

+60

pw::StringBuilder::ResizeAndTerminate()

NEW

+26

pw::StringBuilder::NullTerminate()

NEW

+24

pw::string::(anonymous namespace)::HandleExhaustedBuffer()

NEW

+22

pw::StringBuilder::HandleStatusWithSize()

NEW

+4

__aeabi_ldiv0

+1,584

StringBuilder cost when completely replacing snprintf

FLASH

+52

[section .code]

DEL

-512

_svfprintf_r

-2

_ctype_

DEL

-184

__ssputs_r

-2

__aeabi_d2f

DEL

-108

pw::allocator::FreeListHeap::Realloc()

DEL

-104

snprintf

+92

main

+8

__sf_fake_stdout

DEL

-16

__wrap__realloc_r

NEW

+720

__udivmoddi4

NEW

+276

pw::string::IntToString<>()

NEW

+160

pw::string::(anonymous namespace)::kPowersOf10

NEW

+84

pw::StringBuilder::append()

NEW

+64

pw::string::DecimalDigitCount()

NEW

+60

pw::StringBuilder::ResizeAndTerminate()

NEW

+26

pw::StringBuilder::NullTerminate()

NEW

+24

pw::string::(anonymous namespace)::HandleExhaustedBuffer()

NEW

+22

pw::StringBuilder::HandleStatusWithSize()

NEW

+4

__aeabi_ldiv0

+664

Incremental cost relative to snprintf for 10 strings

FLASH

-44

[section .code]

-100

main

-4

CSWTCH.1

-4

pw::string::DecimalDigitCount()

NEW

+112

pw::StringBuilder::operator<< <>()

NEW

+8

pw::StringBuilder::push_back()

-32

Size comparison: snprintf versus pw::string::Format#

The pw::string::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 pw::string::Format.

Label

Segment

Delta

Format instead of snprintf once, return size

FLASH

DEL

-104

snprintf

-12

pw::string::OutputStringsToBuffer()

NEW

+88

_vsnprintf_r

NEW

+58

pw::string::FormatVaList()

NEW

+38

pw::string::Format()

NEW

+28

vsnprintf

+96

Format instead of snprintf 10 times, handle errors

FLASH

-32

pw::string::OutputStringsToBuffer()

-4

quorem

DEL

-104

snprintf

DEL

-40

(anonymous namespace)::ProcessResult()

NEW

+88

_vsnprintf_r

NEW

+58

pw::string::FormatVaList()

NEW

+38

pw::string::Format()

NEW

+28

vsnprintf

+32

Format instead of snprintf 50 times, no error handling

FLASH

+8

pw::string::OutputStringsToBuffer()

-4

quorem

DEL

-104

snprintf

NEW

+88

_vsnprintf_r

NEW

+58

pw::string::FormatVaList()

NEW

+38

pw::string::Format()

NEW

+28

vsnprintf

+112

Roadmap#

  • StringBuilder’s fixed size cost can be dramatically reduced by limiting support for 64-bit integers.

  • Consider integrating with the tokenizer module.

Compatibility#

C++17, C++14 (pw::InlineString)

Getting started#

GN#

Add $dir_pw_string to the deps list in your pw_executable() build target:

pw_executable("...") {
  # ...
  deps = [
    # ...
    "$dir_pw_string",
    # ...
  ]
}

See //source/BUILD.gn in the Pigweed Sample Project for an example.

Zephyr#

Add CONFIG_PIGWEED_STRING=y to the Zephyr project’s configuration.

Roadmap#

  • The fixed size cost of pw::StringBuilder can be dramatically reduced by limiting support for 64-bit integers.

  • pw_string may be integrated with pw_tokenizer.