Strings and memory recovery

If I use Strings locally, does the memory it was occupying 'free up' once it's no longer required?

I know we sould be avoiding the use of String and use other methods such as char, but I'm just wondering about the memory allocation when Strings are used locally rather than globally.

So is something like this.....

unsigned long newTime = 0;
unsigned long oldTime = 0;

void setup()
{

  Serial.begin(115200);
}

void loop()
{

  newTime = millis();
  if (newTime - oldTime >= 10000) // print myString every 10 seconds
  {
    printMyString();
    oldTime = newTime;
  }
}

void printMyString()
{

  String myString = "This is my String";

  Serial.println(myString);
}

....better for memory than something like this....

unsigned long newTime = 0;
unsigned long oldTime = 0;
String myString = "This is my String";

void setup()
{
  Serial.begin(115200);
}

void loop()
{

  newTime = millis();
  if (newTime - oldTime >= 10000) // print myString every 10 seconds
  {
    printMyString();
    oldTime = newTime;
  }
}

void printMyString()
{
  Serial.println(myString);
}

My feeling it is probably better, but will fragmentation become an issue over time with it?

Thanks

The Evils of Strings article may answer your question.

Perfect mate, thank you :slight_smile:

As always: "It depends."

In the first example you create a "temporary var" every time the printMyString is called – which is in every loop passage. It would be created on stack and destroyed at the end of the function.

It is not bad per se – it is good in general. But this is not a "normal" value like integer, that we can know its size. It is a String object. And when it comes to String object size – we cannot know it in advance (simplification for now). So it is dynamic. And dynamic lives on the heap. Dynamic reserves some space, and frees it later. And it can leave holes (heap fragmentation).

With such simple example, ofc it does not matter. But in general, it might. Especially if you make big HTMLs strings or sth like that.

BUTTT, you need the string in the main loop – so you need it like all the time the application "lives". When it is declared global, it "lives forever". It is created at start, it is not destroyed later and recreated. It is still on heap (because String object), but – because it is not destroyed – it does not leave "holes" that could lead to heap fragmentation.


Sidenotes:

  1. If you want to save RAM, local vars are better – because you do not use memory until it is needed –> first example wins.
  2. If you have spare RAM, global vars can be better – because you reduce heap fragmentation by NOT destroying them -> second example wins.
  3. There are short strings optimizations, so it is OK to use String objects for short strings.
  4. Changing the size of String object can lead to more heap fragmentation and increased usage of RAM – you can avoid if with reserve method if you know the final size in advance.
  5. Use F() macro for string literals String myString = F("This is my String"); to save a bit of RAM as well (more advanced topic).

Thanks for the reply.

Do Strings that are used during setup affect the memory in the same way? I imagine that they do, but it's always worth knowing for sure.

I genrally have debug messages defined so I can just switch them on and off.

yes, setup is just yet another, normal function – same rules apply

Thank you

Sorry to go on.

In this case I'm using esp32 Devkit and VSCode with Arduino framework.

If I hover over a the message I'm printing to serial, it's suggesting that it's printing a 'const char'. Am I right in thinking then that this is stored on the stack and then destroyed after it's printed?

image

So does this mean I don't need to worry about converting: -

"This is my String" to a char if I'm writing Serial.println("This is my String")?

In the case of wanting to print the full timestamp using the RTC for example, I'd need to convert the timestamp to a c-string then print?

eg, something like this

void printTimeMessage()
{
  char dateTimeMessage[42];

  DateTime now = rtc.now();
  sprintf(dateTimeMessage, "The date and time is: %s.", now.timestamp(DateTime::TIMESTAMP_FULL));

  Serial.println(dateTimeMessage);
}

yes -> and that is OK

const char* const the_c_string = "Foo bar baz";

Serial.println(the_c_string);

Is the same memory–wise as just

Serial.println("Foo bar baz");

In the second one the "variable" is created temporarily behind the scenes.

Looks OK to me. It is on stack, so it will be created and destroyed inside the function. It will not leave "holes" on heap.

BUT, you have to be careful specifying the size – when you change the string contents, etc. You are risking memory issues, like buffer overflow.


But since you're using ESP32, you have A LOT of RAM, compared to "basic" Arduino chips. It is good that you want to understand the topic, on how to deal with those issues correctly given, but in practice you can pretty sure do anything with ESP.

You can use String objects as well, why not – I do it extensively on the older ESP8266. We're not building space rockets and using Strings is just so much easier and convenient.

Excellent, thanks again. Yes, I'm aware of the fantastically large RAM on the ESP, but I've got a few Arduinos knocking about that I'd like to use (IOT 33 for example).

Thanks to both you and @groundFungus for your education & time :slight_smile: