String object, functions, heap, stack and Memory Management

Who I am: I mostly have written in Java and have recently graduated college with a BS degree in Mathematics with a Minor in Computer Science. I have basic understanding of stack and pointers, but very little in garbage collection since JRE does this during runtime. Any assistance or corrections are welcome.

For the past week, I have been working on optimizing code and checking to insure that every line in the code is necessary for a specific application/ project to run successfully. In that pursuit, I have read several forum entries ranging from the 2011 to several weeks ago timeframe concerning memory management especially malloc and the dangers of the String object. Most of the entries are related to Arduino Uno or other Arduino microcontrollers with <=2K SRAM. However, I am working with an Arduino Mega 2560 R3 with 8K SRAM. My only concern is that for the project to run successfully for six months once placed into operation is that the heap doesn't get fragmented enough to have stack collision. The code below will be referenced throughout this entry.

//global variables that will be changed during operation
boolean data1 = true;
boolean data2 = true;
boolean data3 = true;
boolean data4 = true;
boolean data5 = true;
boolean data6 = true;
boolean data7 = true;

double data8 = 0.0;
double data9 = 0.0;
double data10 = 0.0;
double data11 = 0.0;
double data12 = 0.0;
double data13 = 0.0;
double data14 =0.0;

byte data15 =0;
byte data16 =0;
byte data17 =0;
byte data18 =0;
byte data19 =0;
byte data20 =0;

unsigned int data21 = 0;
unsigned int data22 = 0;
unsigned int data23 = 0;
unsigned int data24 = 0;
unsigned int data25 = 0;

unsigned long time = 0;
byte counter = 0;
boolean runOnce = true;

char cArray [140];

void setup()
{
      Serial.begin(115200);
      //pinMode will be called to setup the Digital pins to be either INPUT or OUTPUT
      //setup any other sensors and begin the necessary libraries
      time = millis();
}

void loop()
{
      //several functions will be called to set data1, data2,...,data25
      // by either ternary operator, analogRead(...), digitalRead(...), or a combination
      if(abs(millis()-time)>=1000)  // due to aarg comment: not need only to match difference formula
      {
             func1();           // sets data1
             func2();           // sets data2
                 .
                 .
                 .
             func25();          // sets data25
             counter = ((counter+1)%60);
             time=millis();     //this insures that data1, data2,...,data25 are set every second
      }
      // Now, cArray must be built using data1,data2,...,data25
      if(counter==0 && runOnce)
      {
             set_cArray();
             runOnce = false;

       }
       else if(counter==1 && !runOnce)
       {
             runOnce = true;
       }
    
}

void set_cArray()
{
         String s = String(data1) + "," + String(data2) + "," + String(data3) + "," +
                         String(data4) + "," + String(data5) + "," + String(data6) + "," +
                         String(data7) + "," + String(data8) + "," + String(data9) + "," +
                         String(data10) + "," + String(data11) + "," + String(data12) + "," +
                         String(data13) + "," + String(data14) + "," + String(data15) + "," +
                         String(data16) + "," + String(data17) + "," + String(data18) + "," +
                         String(data19) + "," + String(data20) + "," + String(data21) + "," +
                         String(data22) + "," + String(data23) + "," + String(data24)  + "," +
                         String(data25);
         Serial.print(F("Location of s: "));
         Serial.println((int)&s);
         s.toArray(cArray,s.length()+1);

         // Question: on return is String object s deleted automatically off stack or heap?

}

My project uses Adafruit CC3000 WiFi shield with an SD card; thus, I am using Arduino 1.0.5-r2 IDE. set_cArray() in the code builds the cArray char array so that the cArray can be used by a write to SD function (NOT SHOWN) and to the Serial Window. In the near future, cArray will be sent using TCP to a client for monitoring and control. data1,data2,...,data25 are used by a LCD I2C module with a 20 char 4 line LCD, the write to LCD functions are not shown. All not shown code is due to not needed for this entry or questions to follow.

Now, to the nuts and bolts of this entry. In my reading, I have learned that SRAM is split into four parts: Global Variables, Heap, Free space and Stack. The issue that so many users have with String object, I gathered, is that the String object lives in Heap and when freeing Heap space the concatenation of bits/bytes can leave holes leading to heap fragmentation and eventual stack and heap collisions.

What I also have gathered is that functions store the local variables on the stack unless that local variable is created by a library (constructor) that builds the object in the heap. I am not sure about that last statement since I am pretty sure that each stack location is 32 bits 16 bits [verified by econjack] on the Mega; long enough to contain a pointer to a heap location. In set_cArray() function, the pointer to String object 's' stays the same when printed to the Serial Window since every time I call set_cArray the stack pointer is going to be in the same location due to function calls are always the same leading to stack usage is the same for the loop that calls set_cArray().

In the forum thread( Question about the life and memory space of a variable inside a function... - Programming Questions - Arduino Forum ), the user asked about the life of a local variable. However, if an object is stored on the heap with the pointer on the stack when the return pointer is called from the stack to the loop in the case of my code then the pointer is lost but the heap object remains, thus in set_cArray before '}' there should be a delete(s);? Or is the String object 's' entirely on the stack?

Before any comment of don't use String object, I am writing this code for a person with very basic basic understanding of code and he or she will have to update this code once this project goes operational. Thus, he or she will not understand C++ casting, which I have just started trying learn. The commonly suggested alternative to String object is sprintf() function. However, sprintf() on 25 differing data types would require up to 25 differing format specifiers since in real life the four data types are mixed up rather than the displayed code which has them grouped leading to a harder task for the code maintainer to manage. In addition, I am a Math major so Prove it don't just say it!

Thank you ahead of time for any comments or suggestions.

// Edited and attempted to clarify due to comments from PaulS, aarg and econjack.
Aug 20 added References:
Memory Heap: Memory heap - Programming Questions - Arduino Forum
Dangers of String object: String corrupts the heap and crashes - Suggestions for the Arduino Project - Arduino Forum
Memory Management/Optimizing Code: You know you have a memory problem when... | Memories of an Arduino | Adafruit Learning System

The sprintf() function on 25 differing data types

Let's see - int, float, char, string...

Hmmm, that's not 25. Where did you get 25?

Is the abs() really necessary here?
      if(abs(millis()-time)>=1000)No.

...I am pretty sure that each stack location is 32 bits on the Mega; long enough to contain a pointer to a heap location.

Easy enough to check:

void setup() {
  int *ptr;
  Serial.begin(9600);
  Serial.print(sizeof(ptr));
}

void loop() {
}

Hmmm...it appears that pointers only use 2 bytes on a Mega. Also, sprintf() is usually an H-bomb-to-kill-an-ant in most applications. And, as Paul noted: what are your 25 data types?

@PaulS

I was attempting to state that the sprintf() would not be grouped i.e. data1,data2,data3,...,data25 but have the data mixed up leading to format specifiers all mixed up leading to at most 25 different format specifiers. I instead simply wrote "25 differing data types" not "up to 25 differing format specifiers". Thanks for finding that error.

@aarg

You are correct! The abs() function is not needed during unsigned long operations. I understand that however in a thread, I am not going to find it right now, mentioned if you are worried about the millis() cyclic behavior then use the absolute function. The code is easier to read, IMHO, for a novice as the code maintainer is; since the difference equation is an absolute function.

@econjack

Thanks for verifying that. I wasn't sure about that statement after writing it. That was more of a mouth was ahead of the brain or fact checking in that case. Review my comment to PaulS about the 25 mistake.