Error using arrays

Hello.
I'm having trouble with a code using arrays.
These are short programs that illustrate the problem.
I'm using an ESP8266 12E.

The first code works correctly, printing the correct values ​​in both the first and second parts.

String projNome = __FILE__;                                  // Project name
unsigned long logTime;                                       // Work reg
unsigned long intervalo = 2;                                 // convert time
char* reads[] {"0", "0", "0", "0"};                          // Read reg

//---------------------------------------
void setup(void) {
  Serial.begin(115200);                                     // Inicialize serial at 115200
  Serial.println(" ");                                      // Cr Nl
  Serial.println(projNome);                                 // Project name
}
//--------------------------------------------------
void loop() {
  if (millis() - logTime > (intervalo * 1000)) {            // Time to convert
    logTime = millis();                                     // Refresh time
    for (int k = 0; k < 4; k++) {                           // Work 4 times
      String temp_c = "22.55";                              // Default  value
      int varSize = temp_c.length() + 1;                    // Calc size variable
      char* arr = new char[varSize];                        // New array *
      strcpy(arr, temp_c.c_str());                          // Store new value
      reads[k] = arr;                                       // Save new value at read[] array by k
      Serial.print("read_1  "); Serial.print(k);
      Serial.print(": "); Serial.println(reads[k]);         // Print read[] array by k
    }
    for (int m = 0; m < 4; m++) {
      Serial.print("read_2  "); Serial.print(m);
      Serial.print(": "); Serial.println(reads[m]);         // Print read[] array by m
    }
    Serial.println(" ");
  }
}

First part
read_1 0: 22.55
read_1 1: 22.55
read_1 2: 22.55
read_1 3: 22.55

Second part
read_2 0: 22.55
read_2 1: 22.55
read_2 2: 22.55
read_2 3: 22.55

The second code prints the second part incorrectly, even though both printouts come from the same array. Part 1

String projNome = __FILE__;                                 // Project name
unsigned long logTime;                                      // Work reg
unsigned long intervalo = 2;                                // convert time
char* reads[] {"0", "0", "0", "0"};                         // Read reg

//---------------------------------------
void setup(void) {
  Serial.begin(115200);                                     // Inicialize serial at 115200
  Serial.println(" ");                                      // Cr Nl
  Serial.println(projNome);                                 // Project name
}
//--------------------------------------------------
void loop() {
  if (millis() - logTime > (intervalo * 1000)) {            // Time to convert
    logTime = millis();                                     // Refresh time
    for (int k = 0; k < 4; k++) {                           // Work 4 times
      String temp_c = "22.55";                              // Default  value
      int varSize = temp_c.length() + 1;                    // Calc size variable
      char str[varSize];                                    // New array
      char *arr { str };                                    // New array *
      temp_c.toCharArray(str, varSize);                     // Store new value
      reads[k] = arr;                                       // Save new value at read[] array by k
      Serial.print("read_1  "); Serial.print(k);
      Serial.print(": "); Serial.println(reads[k]);         // Print read[] array by k
    }
    for (int m = 0; m < 4; m++) {
      Serial.print("read_2  "); Serial.print(m);
      Serial.print(": "); Serial.println(reads[m]);         // Print read[] array by m
    }
    Serial.println(" ");
  }
}

read_1 0: 22:55
read_1 1: 22:55
read_1 2: 22:55
read_1 3: 22:55

Part 2.
read_2 0: 22:5T⸮⸮?
read_2 1: 22:5T⸮⸮?
read_2 2: 22:5T⸮⸮?
read_2 3: 22:5T⸮⸮?

Since I'm a very basic programmer and learning on my own,
I couldn't understand the reason for the incorrect printout.
I would greatly appreciate any help in resolving this error.
Thank you in advance.

Here is what I found:

reads[k] = arr; stores pointer to stack Fix: Use String or static array
Works first time only: memory overwritten after loop you need non changing storage for it.

Unlike with print, you can call println with no arguments. That will print the CR and LF, without the probably pointless space.

The first sketch happens to work because of this bug: allocating space on the heap with new and never releasing it. Therefore, in the second loop, the string is still there. Eventually though, you'll run out of memory and probably crash when trying to strcpy to a null pointer.

If you allocate a new array there must be a corresponding delete[].

In the second sketch, the temp array is allocated on the stack, along with an unnecessary copy of the same pointer: str is an array, which also "decays" to a pointer. The memory on the stack is laid out for everything happening in the current block; in this case, the for loop with k, along the four other local variables.

Before printing reads[k], it is assigned the pointer into the stack that was just written into by toCharArray. So that works momentarily.

But when the next for loop by m executes, all that memory is "fair game" and can be overwritten. The memory for m could be exactly where the memory for k was. But the memory "further in" on the stack, where str/arr point to, could be reused. Apparently the first four bytes, 22:5 were not altered, but the bytes after were changed.

There are a few ways to improve this. One is to allocate fixed space in the global array; instead of an array of pointers (char*), a two-dimensional array of char

constexpr size_t readLen = 8;
char reads[4][readLen];

then write directly into it

    logTime = millis();
    for (int k = 3; k >= 0; k--) {
      String temp_c = "22.556789";  // will be safely truncated
      temp_c.toCharArray(reads[k], readLen);
      Serial.print("read_1  "); Serial.print(k);
      Serial.print(": "); Serial.print(reads[k]);
      Serial.print('\t'); Serial.println(reinterpret_cast<uintptr_t>(reads[k]), HEX);
    }
    for (int m = 0; m < 4; m++) {
      Serial.print("read_2  "); Serial.print(m);
      Serial.print(": "); Serial.print(reads[m]);
      Serial.print('\t'); Serial.println(reinterpret_cast<uintptr_t>(reads[m]), HEX);
    }
    Serial.println();

To make things interesting, the first loop goes backwards, to prove that toCharArray honors the second argument, to truncate the string if necessary. It also prints the actual address, so you can see how things are laid out in memory.

On an MCU, run time memory allocation is generally a bad idea, as is the use of the String class, because of very limited RAM and the lack of an OS to do garbage collection. If you use either, you can expect crashes and/or inexplicable errors.

Seasoned programmers do their homework, allocate necessary storage with fixed arrays at startup, check for array bound violations at every opportunity and enjoy error free operation with long term stability.

1 Like

Thank you very much for your response.

I will study more about what you said.

Thank you.

Thank you very much for your reply.
My idea is to not use strings, and I'm learning.

Thank you.

Thank you very much for your response.

Thank you

@ruilviana
To make more clear what the bug in your code, try to run second sketch with loop() changes as shown below:

Continuing my learning about arrays and strings and using parts of the code indicated by @kenb4 , I came across this new problem:

read 0 12.3456789
read 1 98.77

In "read 0" I use the value of a string directly, but in "read 1" I use a conversion from a float to a string.

Why are the values ​​in "read 1" printed with missing characters?

Thank you in advance for your help.

constexpr size_t readLen = 11;
char reads[11][readLen];
//---------------------------------------
void setup(void) {
  Serial.begin(115200);                                     // Inicialize serial at 115200
  Serial.println("");                                      // Cr Nl

  String temp_c = "12.3456789";
  for (int l = 0; l < 11; l++) {
    temp_c.toCharArray(reads[0], readLen);
  }
  Serial.print("read 0  "); Serial.println(reads[0]);
  
  float temp_d = 98.7654321;
  for (int l = 0; l < 11; l++) {
    String(temp_d).toCharArray(reads[1], readLen);
  }
  Serial.print("read 1  "); Serial.println(reads[1]);
}
//--------------------------------------------------
void loop() {
}

Unrelated to your issue, but why are you writing to the same array location (index 1) eleven times in a row?

Related to your issue, look in "WString.h" for the prototype of the String object constructor that takes a float:

	explicit String(float, unsigned char decimalPlaces=2);

Note that the default value for decimal places is 2. Try:

    String(temp_d, 4).toCharArray(reads[1], readLen);

Also, note that your expectation of getting 9-significant figure precision from a single-precision float variable is unrealistic:

  float temp_d = 98.7654321;

6 significant figures is about what you can expect.

Tanks for your attention.