efficiency question: char array vs string

Ok, so I am newer to the Arduino and have a question of efficiency of char vs string. Please correct me where I am going wrong with my understanding! I am writing a rather large program and am worried about crashing. I'm trying to optimize where I can, and from what I have read I should avoid strings because of the potential for memory fragmentation.

The part of code that I have question about is when I load my system setpoints from the SD card. This will only occur in the case of a power failure, or a restart. That being said however I will have parts of code that I have yet to write that would follow a similar pattern.

I didn't write all the first code, I did modify it. I forgot where I originally got it from my apologies to the author I tried to find it with no success.

Here is the method that uses strings.

void getParameters() {
  char inChar;
  String parameter;
  String paramValue;
  myFile = SD.open("sysParam.txt");
  if (myFile) {
    while (myFile.available()) {
      inChar = myFile.read();
      while ((myFile.available()) && (inChar != '[')) {
        inChar = myFile.read();
      }
      inChar = myFile.read();
      while ((myFile.available()) && (inChar != ',')) {
        parameter = parameter + inChar;
        inChar = myFile.read();
      }
      inChar = myFile.read();
      while ((myFile.available()) && (inChar != ']')) {
        paramValue = paramValue + inChar;
        inChar = myFile.read();
      }
      if (inChar == ']') {
        Serial.print(F("\nParameter Name: "));
        Serial.print(parameter);
        Serial.print(F("\nParameter Value: "));
        Serial.print(paramValue);
        applySetting(parameter, paramValue);
        parameter = "";
        paramValue = "";
      }
    }
    myFile.close();
  }
  else {
    Serial.print(F("\nError opening systems parameter file"));
    errorCode = 1;
    printError(0, 100);
  }
}

void applySetting(String parameter, String paramValue) {
  if (parameter == "pHL")
    pH_lowerLimit = paramValue.toFloat();
  else if (parameter == "pHU")
    pH_upperLimit = paramValue.toFloat();
  else if (parameter == "DOSet")
    DO_lowerLimit = paramValue.toFloat();
  else if (parameter == "WV")
    wasteVolume = paramValue.toFloat();
  else if (parameter == "RV")
    reactorVolume = paramValue.toFloat();
  else if (parameter == "DV")
    decantVolume = paramValue.toFloat();
  else if (parameter == "sampleRate")
    sampleRate = paramValue.toFloat();
  else if (parameter == "dataRate")
    dataRate = parameter.toFloat();
  else {
    errorCode = 2;
#if DEBUG
    Serial.print(F("\nError: Invalid parameter in *.ini file (2)"));
#endif
    printError(0, 100);
  }
 
  return;
}

and the method using only char

void getData(char dType) {
  char inChar;
  char parameter[4];
  char paramValue[8];
  int count = 0;
  if (dType == 'S')
    myFile = SD.open("sysParam.txt");
  else if (dType == 'F')
    myFile = SD.open("flow.ini");
  if (myFile) {
    while (myFile.available()) {
      inChar = myFile.read();
      while (myFile.available() && inChar != '[') {
        inChar = myFile.read();
      }
      for (int x = 0; x < 3; x++) {
        parameter[x] = myFile.read();
      }
      parameter[3] = '\0';
      inChar = myFile.read();
      inChar = myFile.read();
      while (myFile.available() && inChar != ']') {
        paramValue[count] = inChar;
        inChar = myFile.read();
        count++;
      }
      paramValue[count] = '\0';
      count = 0;
      if (inChar == ']') {
        Serial.print(F("\nParameter Name: "));
        Serial.print(parameter);
        Serial.print(F("\nParameter Value: "));
        Serial.print(paramValue);
        applySetting(parameter, paramValue);
        parameter[0] = '\0';
        paramValue[0] = '\0';
      }
    }
    myFile.close();
  }
  else {
    Serial.print(F("\nError opening systems parameter file"));
  }
}

void applySetting (char parameter[], char paramValue[]) {
  if (parameter[0] == 'p' && parameter[1] == 'H' && parameter[2] == 'L')
    pH_lowerLimit = atof(paramValue);
  else if (parameter[0] == 'p' && parameter[1] == 'H' && parameter[2] == 'U')
    pH_upperLimit = atof(paramValue);
  else if (parameter[0] == 'd' && parameter[1] == 'O' && parameter[2] == 'L')
    DO_lowerLimit = atof(paramValue);
  else if (parameter[0] == 'w' && parameter[1] == 'M' && parameter[2] == 'L')
    wasteVolume = atof(paramValue);
  else if (parameter[0] == 'r' && parameter[1] == 'M' && parameter[2] == 'L')
    reactorVolume = atof(paramValue);
  else if (parameter[0] == 'd' && parameter[1] == 'M' && parameter[2] == 'L')
    decantVolume = atof(paramValue);
  else if (parameter[0] == 's' && parameter[1] == 'S' && parameter[2] == 'R')
    sampleRate = atof(paramValue);
  else if (parameter[0] == 'd' && parameter[1] == 'S' && parameter[2] == 'R')
    dataRate = atof(paramValue);
  else if (parameter[0] == 'a' && parameter[1] == 'F' && parameter[2] == 'R')
    acidFlowRate = atof(paramValue);
  else if (parameter[0] == 'b' && parameter[1] == 'F' && parameter[2] == 'R')
    baseFlowRate = atof(paramValue);
  else if (parameter[0] == 'w' && parameter[1] == 'F' && parameter[2] == 'R')
    wasteFlowRate = atof(paramValue);
  else if (parameter[0] == 'd' && parameter[1] == 'F' && parameter[2] == 'R')
    dilutionFlowRate = atof(paramValue);
  else if (parameter[0] == 'f' && parameter[1] == 'F' && parameter[2] == 'R')
    feedFlowRate = atof(paramValue);
  else {
    errorCode = 2;
    Serial.print(F("\nInvalid parameter in file"));
    printError(0, 100);
  } 
}

What I'm doing is reading a file that has the following format [parameter,parameterValue]
parameter will always be 3 characters, the parameter value will differ from parameter to parameter in length.

I also realize that I am sending a char to the function in example #2. Thats because I had to rewrite the function because I am storing different setpoints in different files. For the types of setpoints it makes sense to do it that way.

Both sets of code work fine, no issues taking in the data.

Which one is more efficient? Does it matter? I'm think I have some understanding of the issues with memory fragmentation and stack overflow, but yet again I think I could be completely wrong.

Thanks for the help!

Juhall260:
Which one is more efficient?

Second.

Does it matter?

There is not enough information for anyone on this side of your monitor to answer that question. For example, the first version presumably took less time for the developer to code and debug. If development cost is an issue then the first one may be a better choice. However, odds are very high the second one is a better choice.

I'm think I have some understanding of the issues with memory fragmentation and stack overflow, but yet again I think I could be completely wrong.

The second one cannot possible fragment the heap because it does not use the heap.

Any function call has the potential to overflow the stack.

You need to bound this...

      while (myFile.available() && inChar != ']') {
        paramValue[count] = inChar;
        inChar = myFile.read();
        count++;
      }

Thank you. You're right. Development costs are not an issue, well not so much. For the sake of this argument I'm 100% concerned about code efficiency.

Since you've coded both methods, why don't you just time them; get a start time using millis(), and an end time, and see which one is quicker.

If by "efficient" @Juhall260 meant "memory efficient" then such a test is not necessary.

You can have a look at (in Windows) the file C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino\WString.cpp to get an idea what goes on when you use String (capital S).

The difference in number of code bytes is an indication as to what is more efficient. Do not look at the number of bytes in dynamic memory as that is no indication.

As said above, you can also simply time it.

Out of curiosity, I wrote below code (comment / uncomment the first line for the different effects)

//#define USESTRING

char buffer[256];
String strText = "";

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

  unsigned long startTime = micros();

#ifdef USESTRING
  /*
    Sketch uses 3,928 bytes (12%) of program storage space. Maximum is 32,256 bytes.
    Global variables use 227 bytes (11%) of dynamic memory, leaving 1,821 bytes for local variables. Maximum is 2,048 bytes.

    one loop  1688us
    two loops 3292us
  */
  for (int cnt = 'a'; cnt <= 'z'; cnt++)
  {
    strText += cnt;
  }
  for (int cnt = 'a'; cnt <= 'z'; cnt++)
  {
    strText += cnt;
  }
#else
  /*
    Sketch uses 3,612 bytes (11%) of program storage space. Maximum is 32,256 bytes.
    Global variables use 483 bytes (23%) of dynamic memory, leaving 1,565 bytes for local variables. Maximum is 2,048 bytes.

    one loop   96 us
    two loops 108 us 
  */
  for (char cnt = 'a'; cnt <= 'z'; cnt++)
  {
    buffer[cnt - 'a'] = cnt;
  }
  for (char cnt = 'a'; cnt <= 'z'; cnt++)
  {
    buffer[cnt - 'a'] = cnt;
  }
#endif

  Serial.print("duration ");
  Serial.println(micros() - startTime);
}

void loop()
{

}

I've included the compile result and the run time result in the code.

If you make it a pure character array code (getting rid of the String variable), you will save over 1700 bytes of code memory.

I leave it up to you write something that better reflects your real scenario and draw your conclusions.

Juhall260:
Ok, so I am newer to the Arduino and have a question of efficiency of char vs string.

You mean String, right? Sorry to be pedantic, but they are different things.

If you are worried about crashing, don't use String.