StaticString – Beyond Basics
The StaticString library is pretty easy to use, however there’s a couple of little gotchas and interesting points.
The first is copying!
Copying StaticStrings
C-strings are just pointers to character arrays. Creating a new char pointer to the string, of course, will only ‘shallow-copy’.
1 2 3 4 5 |
char* c_s1 = "Hello"; char* c_s2; c_s2 = c_s1; c_s2[0] = 'Y'; cout << c_s2 << endl; //prints 'Yello' |
Modifying c_s2 will also modify c_s1 because they are both pointing to the same address in memory.
StaticStrings can only deep-copy.
1 2 3 4 5 6 7 |
StaticString<20> s1; StaticString<20> s2; s1 = "Hello"; s2 = s1; s2[0] = 'Y'; cout << s1.c_str() << endl; //prints 'Hello', because s1 is unaffected by changes to s2 |
Every byte (up to and including the null terminator at s1[6] ) is copied from s1 to s2.
When C-strings are passed as functions parameters it’s only a pointer that is passed.
1 2 3 4 5 6 7 8 |
void foo(char* cstr) { strcpy(cstr, "Bye, world"); } char* str = "Hello world"; foo(str); //send a copy of the pointer to foo() //str is now 'Bye, world' |
And when StaticStrings are passed as parameters they are, of course, deep-copied.
1 2 3 4 5 6 7 8 9 |
void foo(etk::StaticString<20> str) { str = "Bye, world"; } etk::StaticString<20> ss; ss = "Hello world!"; foo(ss); //ss still contains 'Hello world!' |
By passing ss to foo, a whole new StaticString is created and ss is deep-copied to it. This means at least an extra 24 bytes is added to the stack, and every byte (up to the null terminator) from ss is copied to this new space. It’s a lot more efficient to simply pass a reference like this
1 2 3 4 5 6 7 8 9 10 11 |
void foo(etk::StaticString<20>& str) { str = "FOO"; } int main() { etk::StaticString<20> ss = "Hello world!"; foo(ss); //ss now contains 'FOO' } |
Reference passing only pushes a handful of extra bytes (usually around four) onto the stack and no bytes are copied. This is a lot more efficient, so where possible use references to pass strings.
Now let’s say we’ve got strings of different sizes. We’re copying type etk::StaticString<20> to etk::StaticString<80>. Normally that would lead to compiler errors, but StaticString overloads the cast operator to perform a deep copy. This means StaticStrings of different maximum lengths can be used seamlessly together.
1 2 3 4 5 6 7 8 9 10 |
void foo(etk::StaticString<80> ss) { ss = "FOO"; } int main() { etk::StaticString<20> ss = "Hello world!"; foo(ss); } |
UNTIL you start trying to pass references. This example won’t compile . . .
1 2 3 4 5 6 7 8 9 10 |
void foo(etk::StaticString<80>& ss) { ss = "FOO"; } int main() { etk::StaticString<20> ss = "Hello world!"; foo(ss); } |
If you really would prefer to pass references to and from strings of any length, you must use templates or the auto specifier.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
template <uint32_t N> void foo(etk::StaticString<N>& ss) { ss = "FOO"; } int main() { etk::StaticString<20> ss = "Hello world!"; foo(ss); etk::StaticString<80> s2 = "Hello"; foo(s2); } |
or using C++11
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void foo(auto& ss) { ss = "FOO"; } int main() { etk::StaticString<20> ss = "Hello world!"; foo(ss); etk::StaticString<80> s2 = "Hello"; foo(s2); } |
Using StaticString With Other Libraries
More than likely you’ll need to use StaticStrings with other libraries. You can get a constant pointer to a C string using the c_str() function.
1 2 |
etk::StaticString<20> ss = "Hello world!"; const char* s = ss.c_str(); |
If you need to actually modify the StaticString from a pointer, there is the raw_memory() function that returns a char*. It’s far better to use the array access operators, though, since they protect against buffer overruns.
1 2 3 4 5 |
etk::StaticString<20> ss = "Hello world!"; char* s = ss.raw_memory(); s[0] = 'Y'; //don't do this ss[0] = 'Y'; //prefer this instead |