Rewriting char array of strings gets scrambled...

Hi, I have a data logger sketch that uses standard labels to record data on a SD file.
But at print time more relevant labels must be shown.

I have two char arrays, one with the fix labels and the other with the printable version.
If the config file that holds the printable version is not available it will print with the standard labels.

The sketch is quite big but I was able to make a little version that produces the same problem.

By what I have been reading it is sure a result of my poor knowledge of the workings of char arrays, pointers and related no so easy to understand concepts, I am certainly writing or reading from some place else other then the address I desire.

Assistance welcome.
Thanks
Paulo

char*     Labels[] = {"Area01", "Area02", "Area03", "Area04", "Area05", "Area06"};   // Labels MUST have same number of characters
const int NumberOfAreas = (sizeof(Labels) / sizeof(Labels[0]));
char*     prnLabels[NumberOfAreas];

void setup() {
  initArrayVars();
  
  // Init serial monitor
  Serial.begin(115200);
}

String fetchLine(String label, int indexlabelSize) {    //Fetch whole line from text file according to label
/*
  File configFile;
  String line;
  if (SD.exists("/config.txt")) {
    configFile = SD.open("/config.txt", FILE_READ);
  } else {
    line = "FetchLine: Config não encontrado.";
    Serial2.println(line);
    return line;
  }
  while (configFile.available()) {
    line = configFile.readStringUntil('\n');
    if (line.substring(0, indexlabelSize) == label) {
      return line;
    }
  }
  */
  //This function fetches and returns a single line from a "config.txt" file.
  return "LABELS,Camp_A,Camp_B,Camp_C,Camp_D,Camp_E,Camp_F";
}

void loadLabels() {
  String line = fetchLine("LABELS",6);
  int lineSize = line.length();
  char buf[lineSize];
  int counter = 0;
  line.toCharArray(buf, lineSize);
  Serial.println(buf);
  const char s[2] = ",";
  char *token;
  /* get the first token */
  token = strtok(buf, s);
  /* walk through other tokens */
  while ( token != NULL ) {
    token = strtok(NULL, s);
    if (token != NULL) {
      prnLabels[counter] = token;
      counter++;
    }
  }

  Serial.print("Loaded Labels: ");
  for (int i = 0; i < NumberOfAreas; i++) {
    Serial.print(prnLabels[i]);
    if (i < NumberOfAreas - 1) {
      Serial.print(",");
    }
  }
  Serial.println("");
}

void initArrayVars() {    //init array based variables
  for (int i = 0; i < NumberOfAreas; i++) {
    prnLabels[i]        = Labels[i];
  }
}
void loop() {

    Serial.println("Loaded Labels -before: ");
    for (int i = 0; i < NumberOfAreas; i++) {
      Serial.print(prnLabels[i]);
      if (i < NumberOfAreas - 1) {
        Serial.print(",");
      }
    }
    Serial.println("");
      
    loadLabels();

    Serial.print("Loaded Labels -after: ");
    for (int i = 0; i < NumberOfAreas; i++) {
      Serial.print(prnLabels[i]);
      if (i < NumberOfAreas - 1) {
        Serial.print(",");
      }
    }
    Serial.println("");
    Serial.println("");
    delay(15000);
}

"prnLabels" is an array of pointers. In "loadLabels()" you set those pointers to refer positions in a local variable "buf" (via "token"). As soon as "loadLabels()" exits "buf" is no longer valid and neither are the pointers (in "prnLabels") to it. You have two options: Either you declare "buf" as a global variable with the length of the longest possible line OR you declare "prnLabels" as an array of cstrings and copy the labels into those strings. And another tip: Do not use the "String" class :slight_smile:

Dear Danois90, thanks very much for the light, I see what you say.
Now I have searched for "array of cstrings", what is a cstring?
Is it named like that because it ends with '\0'?

I assumed that by declaring prnLabels as global its contents (and ability to change them) would also be global.

How making buf global will make all that difference?

At: prnLabels[counter] = token;
If I COPY "token" into "prnLabels[counter]" instead of using "=" would it make a difference?

"OR you declare "prnLabels" as an array of cstrings and copy the labels into those strings"
Individual strings, one for each label? How do I do that?

Yeah, I know about "String", thanks, as soon as I have this problem sorted out I will have to find some time to refine the code.
Paulo

cstring is an array of char :slight_smile:

You should consider to modify "fetchLine()" to simply return the tokens instead of first finding and loading a line and then tokenize that line. You should also ditch any use of the "String" class. It would probably also be a good idea to open the config file once instead of opening and closing it over and over.

Dear Danois90, thanks for the orientations.
In fact I started by loading all environment variables in one go but as I found the problem above I separated one line of the config at a time as a way to diagnose the problem.

For now I have created a global "buf" variable and it solved the problem, thanks.
The Sketch will require several improvements as I better understand the mechanics behind the char string, which I have to confess, I still do not entirely.

I understood why the "buf" as global solved the issue but failed to understand why, "prnLabels", even if declared just like "Labels", with all its fields populated, cannot hold its own values.

If I declare global:
char* prnLabels[] = {"Area01", "Area02", "Area03", "Area04", "Area05", "Area06"};

and then latter in one of the functions:
prnLabels[0] = Var1 (of char type = "abc")

Why when the function ends and Var1 disapears "abc" is not stored in place of "Area01"?

But anyway, advanced a lot, thanks for your assistance.

Regards
Paulo

If I declare global:
char* prnLabels[] = {"Area01", "Area02", "Area03", "Area04", "Area05", "Area06"};

and then latter in one of the functions:
prnLabels[0] = Var1 (of char type = "abc")

Why when the function ends and Var1 disapears "abc" is not stored in place of "Area01"?

We can't see the code that is doing this, so it is pointless for you to ask questions about the code.

"of char type = "abc"" makes no sense. A variable might of of char type, in which case it can NOT hold "abc". Or, it could be of type array of char, in which case it MIGHT hold "abc". But, the value is NOT the type.

When you declare a variable with "" it means that it is a pointer to a variable of the type used. The declaration "char prnLabels[]" is an array of pointers to "char" variables. When you initialize it as "char* prnLabels[] = { "String1", "String2" }" you have to pointers to two strings. If you later do a "prnLabels[0] = "String3"" then you are not replacing "String1" with "String3" - you are setting the pointer to point at a different string and you loose track of "String1" which will then become wasted memory. Issuing a "sizeof(prnLabels[0])" will always yield 2 on an arduino since a pointer is basically an "unsigned int" which points to a memory location where the actual content of the variable is stored.

char* labels[] = { "String1", "String2", "String3", "String4" };

//Size of a pointer = 2
Serial.println(sizeof(labels[0]));

//The memory address of "String1"
Serial.println((unsigned int)labels[0]);

//The memory address of "String2" = address of "String1" + length of "String1" (8)
Serial.println((unsigned int)labels[1]);

//Set labels[0] to point at something different - after this assignment
//"String1" is unreferenced but used memory (wasted)
labels[0] = labels[3];

//The memory address of "String4" = address of "String3" + length of "String3"
Serial.println((unsigned int)labels[0]);

I have not tested the code, the compiler may perform optimizations which causes the memory locations to be different than expected.