04/08/2021

How To Fix Memory leak in ESP8266/NodeMCU

By snorlaxprime

One problem when you are using micro controllers such as Arduino or ESP8266 is that you need to be aware of how precious the memory is. It not like PC where you can slack of on using it the memory without knowing on how to free the memory once you are done using them.

One culprit in such scenario is using String as part of your variable. So what is a STRING? String in C++ term is a series of characters terminated by “\0” or NULL character at the end of the storage. Here is the link to the Stringobject as defined in Arduino reference. https://www.arduino.cc/reference/en/language/variables/data-types/stringobject/

In another words, string is a lazy way of defining a character storage when you are not sure how big the space you need to allocate. In reality we should know how big of a space we need to allocate the string to.

Consider the following example code in NodeMCU

// the setup section
String serial_data_read = "";   // for incoming serial string data
String serial_data = "";   // for incoming serial string data

// the loop section
if ((last_weight_time < millis() - 1000)) {
   last_weight_time = millis();
   serial_data_read = "";
   Serial.print("P");
   while(Serial.available()) {
      character = Serial.read();
      serial_data_read.concat(character); '<< this part will be responsible for the leak
   }
   serial_data = serial_data_read;
}

When you instantiate a String object with String serial_data_read = "", Arduino String class create a memory allocation using dynamic memory allocation in the heap, and since your initialised it with “”, it therefore create a minimum size of allocation. This is all good if you know how to the dynamic memory is created and how it going to be used.

The problem started when you start doing String concatenation with the line serial_data_read.concat(character), the original allocation for your global variable serial_data_read is no longer have enough space for the concatenated variable, so it create a temporary variable in the heap and do the concatenation, and then eventually put the result back to this newly allocated memory for serial_data_read on the heap, leaving a hole at the heap for the original serial_data_read variable (and the temp variable).

What make this worst is that this is happening for every 16 iterations of the while loop. Theoretically the old serial_data_read prior the concatenation should be free-up at some point (e.g. if your while loop is within a function, the part of the heap might be free-up at the exit of the function), it however does not necessary be the case because your newly created serial_data_read stay on top of piles of to-be-free-up memory that blocking the free up(think of a piece of cheese with a lot of holes on it), so the heap memory get less and less at each iteration.

Short term fix – Not to use String concatenation in a loop.

String class is not always evil if you know what’s going on and how to use it. One of the quick fix for your problem is simply do not do String concatenation within a loop. And if you are using it within a function, keep it local. This will minimise the heap fragmentation.

if ((last_weight_time < millis() - 1000)) {
   last_weight_time = millis();
   if (Serial.available() > 0) {
      String serial_data_read = Serial.readStringUntil('\n');
   }
}

BTW, you can add ESP.getFreeHeap() in your loop() to check if there is still memory leak.

Long term fix – Don’t use String class

For a better fix, and especially for some one new to the programming or C/C++, do not use String class and learn how to use c string and array. Here is the version without using String class.

length = 80;
char buffer[length] = {0};
if ((last_weight_time < millis() - 1000)) {
   last_weight_time = millis();
   while(Serial.available()) {
      Serial.readBytesUntil('\n', buffer, length);
   }
}

Other resources to learn about

https://www.cplusplus.com/reference/cstring/ all the functions for c string and array manipulation to replace Arduino String class.