Using reserve() efficiently

In order to prevent memory fragmentation, I'm allocating 100 bytes of buffer to the String object.
For each iteration, I'm explicitly allocating the 100 bytes by calling the function reserve(100). Can I call this function once only in setup() to allocate the 100 bytes to the String object? If yes then does the compiler reserve 100 bytes throughout the lifetime of the program?
Thanks in Advance!

String value((char *)0);
void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  
  value.reserve(100); //Iteration 0
  value += "Started!";
  Serial.println(value);
  value.clear();
}

void loop() {
 
  value.reserve(100); //Iteration 1
  value += "Hi";
  Serial.println(value);
  value.clear();

  value.reserve(100); //Iteration 2
  value += "Hello";
  Serial.println(value);
  value.clear();

  value.reserve(100); //Iteration 3
  value += "I'm Arduino!";
  Serial.println(value);
  value.clear();

  delay(1000);
}

Hello

Of course you can call it only once in setup, as long as the String length is less than 100 characters it will not be reallocated

In this example it is only used once, while the string is appended to multiple times.

Yes.
Yes.

yourStringName.concat() puts things into the buffer and yourStringName = "" empties the buffer.

Does it? It could just do String[0] = '\0';

Yes.

Very inefficient, could we have a link to source please?

I'll have to back and find where I got the thing. Something I may or may not do.

Cool, works for me either way

It's not done by the compiler but memory is allocated at run time, on the heap.
This is a minimum, if you store more than 100 characters, the String will grow and thus more memory will be used.

No there is a bug (or feature) in the String class (at least on AVR) where it does not detect the insertion of null chars by using the array notation.

run this code if you want to see it:

String test;

void printTest() {
  Serial.write('[');
  Serial.print(test);
  Serial.print(F("]\tLength = ")); Serial.println(test.length()); // 0
}

void setup() {
  Serial.begin(115200); Serial.println();
  test.reserve(100);
  
  test = "Hello World";
  printTest(); // [Hello World]  Length = 11
  
  test = "";
  printTest(); // []  Length = 0
  
  test = "Hello World Again";
  printTest(); // [Hello World Again]  Length = 17
  
  test[0] = '\0';
  printTest(); // [ello World Again]  Length = 17 !! BUG (or feature)
}

void loop() {}

Serial Monitor (@ 115200 bauds) will show

[Hello World] Length = 11
[] Length = 0
[Hello World Again] Length = 17
[ello World Again] Length = 17

2 Likes

This makes sense, String class should have string length stored somewhere, and if Serial prints string as array of char until stored length it will print all chars regardless of '\0'. The question it does = "" actually clear the buffer or just length = 0 (and String[0] = '\0')

That’s exactly what happens.

When you assign “” to the String a new Empty string is created through copy and moved into the existing buffer, thus changing also the length

Would a new empty String be created when using a String buffer? Instead would not the Existing String Memory Location be emptied?

That’s the Code for a cString

String & String::operator = (const char *cstr)
{
	if (cstr) copy(cstr, strlen(cstr));
	else invalidate();
	
	return *this;
}

If you assign an existing String, that would be


String & String::operator = (const String &rhs)
{
	if (this == &rhs) return *this;
	
	if (rhs.buffer) copy(rhs.buffer, rhs.len);
	else invalidate();
	
	return *this;
}

#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__)
String & String::operator = (String &&rval)
{
	if (this != &rval) move(rval);
	return *this;
}

String & String::operator = (StringSumHelper &&rval)
{
	if (this != &rval) move(rval);
	return *this;
}
#endif

I avoid using String, since it corrupt the memory over time.
Take a look at Pstring which solve this problems.

When a 'String ' object is created, a block of memory is allocated (on the heap) which is big enough to contain all it's contents. When the String is expanded, a new block is allocated big enough to contain it's contents. When a A String is reduced in size, the data is not moved around, and the allocated space remains the same.

String theString = "0123456789";  // allocated 10 bytes
theString = ""; // still 10 bytes are allocated
theString = "01234"; // still 10 bytes are allocated
theString = "0123456789abc" // now 13 bytes are allocated

if the block can be expanded because there is space on the heap just beyond its size, that is what will happen, if the memory above the original block is not available, a new block will be allocated and the data will be stored in there, releasing the space of the original block, which may be re-used by an object that requires the same or less space than the original block.

String newString = "01234567"; // should take up most of the space that was release by theString 

All that reserve() does is expand the size of the allocated memory to the specified size, without increasing the actual size or modifying the data.

String theString = "0123456789";  // allocated 10 bytes
theString.reserve(15); // now 15 bytes are allocated
theString = "0123456789abc" // still 15 bytes are allocated

This of course deals mainly with 'globally' declared String object, the local variables are still destroyed when the function is completed
All in all one can say that by using reserve() you prevent the fragmentation of the memory by the String class, as long as you do make sure that the String variable does not exceed the size you have reserved for it. I suggest you make sure of that by checking on the (future) size before expanding it.

that is if you don't use the '+' operator to feed content into that String as this could generate dynamically allocated string you don't see

String test;
test.reserve(50);
test = "Hello"; 
test = test + " world"; // 2 new temporary Strings created and deleted to perform this

Yes but since test is already allocated at 50 bytes, the remaining String called 'test' is still in the same place with the same allocation, so no fragmentation of that String has occurred. Without the reserve(50) it would be a nice mess.

yes - without reserve() there would be one additional heap poking contest :slight_smile: