For example,
String message = String("xxxx") + String("yyyyy");
Is this all done without using the heap (and associated fragmentation issues)?
not at all
String("xxxx") ➜ allocates a String
String("yyyyy") ➜ allocates a String
+ ➜ allocates a hidden String that is the concatenation of the other two
finally String message ➜ allocates a String and makes the copy of the hidden String
you can't get much worse than this ![]()
use fixed size cString if you want to avoid messing with the heap.
if you really need to handle Strings, then it's better written this way
String message;
message.reserve(50); // reserve 50 bytes in one go (enough space)
message = "xxxx";
message += "yyyy";
I would do
char message[50] = "xxxx"; // allocates 50 bytes. ensure you don't overflow
strlcat(message, "yyyy", sizeof message);
Hi J-M-L,
good explanation what happends "under the hood" when using Strings in different ways
I looked up strlcat here
https://www.qnx.com/developers/docs/7.1/index.html#com.qnx.doc.neutrino.lib_ref/topic/s/strlcat.html
this page says
The strlcpy() and strlcat() functions copy and concatenate strings respectively. They're designed to be safer, more consistent, and less error-prone replacements for strncpy() and strncat().
Unlike those functions, strlcpy() and strlcat() take the full size of the buffer (not just the length) and guarantee to NUL-terminate the result (as long as size is larger than 0 or, in the case of strlcat(), as long as there's at least one byte free in dst).
You should include a byte for the NUL in size. Also note that strlcpy() and strlcat() operate only on true “C” strings. This means that for strlcpy(), src must be NUL-terminated, and for strlcat(), both src and dst must be NUL-terminated.
My opinion: if you suggest using cString to a beginner you should include all the information to make it safe to use or post code that has all the safety-things included
And this is the reason why I prefer to use of the SafeString-library.
- SafeString-library requires some extra- RAM-memory in the range of 50 to 100 bytes
- has a different syntax than if you would use variables of type String
// SafeStrings are based on array of chars
// SafeStrings offer almost the same comfort as Strings
// but avoid some dis-advantages of varable-type String
// the name SafeString is PROGRAM They are safe to use
// with the alternatives "String" must not awlays but CAN
// eat up memory over time which will make the code crash
// with zero-terminated array of chars (c_string)
// you have to take care of boundary checking yourself
// otherwise your code will start to behave weird and this kind of bug is very hard to find
// you can read more about this here https://www.forward.com.au/pfod/ArduinoProgramming/ArduinoStrings/index.html#safestring
// and here https://hackingmajenkoblog.wordpress.com/2016/02/04/the-evils-of-arduino-strings/
// very basic demo-code to show how to declare a SafeString-variable
// and how to assign text to them
// as a personal convention I use the suffix "_SS" to indicate
// THIS IS A SAFESTRING-VARIABLE
// but you can name it whatever you like
#include "SafeString.h"
createSafeString(myTitle_SS, 64); // reserve 64 bytes for the SafeString-variable
createSafeString(myString_SS, 32); // reserve 32 bytes for the SafeString-variable
int myInteger = -1234;
float myFloat = -987.009;
void setup() {
Serial.begin(115200);
Serial.println( F("Setup-Start") );
Serial.println();
myString_SS = F("fixed text directly assigned");
Serial.print( F(" #") ); // leading double-cross "#" to show where the string starts
Serial.print(myString_SS);
Serial.println(F("#") ); // trailing double-cross "#" to show where the string REALLY ends
myTitle_SS = F("content of an integer:");
myString_SS = myInteger;
Serial.println(myTitle_SS);
Serial.print( F(" #" )); // leading double-cross "#" to show where the string starts
Serial.print(myString_SS);
Serial.println( F("#") ); // trailing double-cross "#" to show where the string REALLY ends
myTitle_SS = F("content of a float:");
myString_SS = myFloat;
Serial.println(myTitle_SS);
Serial.print( F(" #" )); // leading double-cross "#" to show where the string starts
Serial.print(myString_SS);
Serial.println( F("#") ); // trailing double-cross "#" to show where the string REALLY ends
myTitle_SS = F("you can append more text with the +=-operator ");
myString_SS = F("text ");
myString_SS += myInteger;
myString_SS += F(" ,");
myString_SS += myFloat;
Serial.println(myTitle_SS);
Serial.print( F(" #" )); // leading double-cross "#" to show where the string starts
Serial.print(myString_SS);
Serial.println( F("#") ); // trailing double-cross "#" to show where the string REALLY ends
}
void loop() {
}
best regards Stefan
That last allocation and copy may not happen because of Move Construction:
String::String(String &&rval)
{
init();
move(rval);
}
So what does this mean in the end?
variable message does not contain the full string "xxxxyyyy"? but only "xxxx"
variable message stays empty?
variable message does not use new memory?
The null is in the 50 as I mentioned the use of cStrings which are null terminated char arrays.
Everyone learning C or C++ should know about cString - may be you should read about those.
The code I posted won't crash nor overflow the buffer even if the second cString is too long.
(BTW The code you posted does not check for concatenation error you should include those if you want to handle errors and be safe)
I would not recommend SafeStrings to beginners, cStrings are better for those who want to learn and Strings are good enough for most beginners' use on AVR for those who don't care.
I'm not one to second guess the compiler, but here's what I think is a plausible story of what happens:
- Two temporary String objects are created from the character literals "xxxx" and "yyyyy" using the String constructor:
String(const char *cstr = "");
- The contents of the second temporary is concatenated onto that of the first with the call to:
String & operator += (const String &rhs) {concat(rhs); return (*this);}
This results in an eventual call to realloc() so the buffer space of the first temporary can be expanded to accommodate the concatenation.
- The String object 'message' is move-constructed using the contents of the first temporary:
String::String(String &&rval)
{
init();
move(rval);
}
- The space allocated to the second temporary is returned to the system by the class destructor:
String::~String()
{
if (buffer) free(buffer);
}
Good point
This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.