0

I get an unpredicted behaviour of a program while trying to fill a dynamic array of String objects. I've tried to simplify the program, but couldn't figure out the cause.

FYI: debug() and debugLn() print data to Serial, debugMem() prints free memory amount.

So, the simplest version works as expected (the scenario - allocate a global array of 3 Strings, define 3 local variables and assign their values to array elements):

String* arr;

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

  debugMem("1");

  String src = "abcdefghijk";

  arr = malloc(3 * sizeof(String));

  debugMem("2");

  String dest1 = src;
  String dest2 = src;
  arr[0] = src.substring(0);
  arr[1] = dest1.substring(0);
  arr[2] = dest2.substring(0);

  debugMem("3");

  dest1.setCharAt(1, 'z');
  dest2.setCharAt(1, 'x');

  debug(F("src: "));
  debugln(src);
  debug(F("src located at "));
  debugln((long)&src);
  debug(F("dest1: "));
  debugln(dest1);
  debug(F("dest1 located at "));
  debugln((long)&dest1);
  debug(F("dest2: "));
  debugln(dest2);
  debug(F("dest2 located at "));
  debugln((long)&dest2);
  debug(F("arr[0]: "));
  debugln(arr[0]);
  debug(F("arr[0] located at "));
  debugln((long)(arr + 0));
  debug(F("arr[1]: "));
  debugln(arr[1]);
  debug(F("arr[1] located at "));
  debugln((long)(arr + 1));
  debug(F("arr[2]: "));
  debugln(arr[2]);
  debug(F("arr[2] located at "));
  debugln((long)(arr + 2));
  debugMem("4");
}

Its output:

>>>>>>>>>>1787<<<<<<<<<<1
>>>>>>>>>>1753<<<<<<<<<<2
>>>>>>>>>>1683<<<<<<<<<<3
src: abcdefghijk
src located at 2294
dest1: azcdefghijk
dest1 located at 2288
dest2: axcdefghijk
dest2 located at 2282
arr[0]: abcdefghijk
arr[0] located at 493
arr[1]: abcdefghijk
arr[1] located at 499
arr[2]: abcdefghijk
arr[2] located at 505
>>>>>>>>>>1683<<<<<<<<<<4

(Though I'm a bit confused with the addresses of src, dest1, dest2 that is greater then the total amount of 2048 bytes of RAM).

The second version does actually the same (pushing values from local var to global array), but its behaviour is "completely different" (the scenario - init a dummy source string, parse it in a function, reallocating a global array to store parsed strings, assign a value from local variable to every array element):

String* phones;
int phonesCount;

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

  String reply = "1234567890,2345678901,3456789012";

  fillPhones(reply);
}

void loop() {
  debugln(F("loop"));

  for(int i = 0; i < phonesCount; i++) {
    debug(F("Phone"));
    debugln(i);
    debug(F("Phone located at "));
    debugln((long)(phones + i));
    debug(F("Phone: "));
    debugln(*(phones + i));
  }
}

void fillPhones(String reply) {
  phonesCount = parsePhones(reply, phones);  
}

int parsePhones(String reply, String* &phonesArray) {
  debugMem("1");

  phonesArray = NULL;
  int count = 0;

  int fromIndex = 0;
  int toIndex = 0;
  while(toIndex >= 0) {
    debug(F("Phone index: "));
    debugln(count);

    toIndex = reply.indexOf(F(","), fromIndex);
    debug(F("Substring: "));
    debug(fromIndex);
    debug(F(".."));
    debugln(toIndex);
    debugMem("2");

    String entryPhone = reply.substring(fromIndex, toIndex);
    debugMem("3");
    debug(F("Phone to add: "));
    debugln(entryPhone);
    debug(F("Phone located at "));
    debugln((long)&entryPhone);

    String* tmpArray = realloc(phonesArray, (count + 1) * sizeof(String));
    if(tmpArray == NULL) {
      break;
    }
    debug(F("Array allocated bytes: "));
    debugln((count + 1) * sizeof(String));
    debug(F("Array located at "));
    debugln((long)tmpArray);
    debugMem("4");

    phonesArray = tmpArray;
    debugMem("5");

    phonesArray[count] = entryPhone;
    debug(F("Added phone: "));
    debugln(phonesArray[count]);
    debug(F("Added phone located at "));
    debugln((long)(phonesArray + count));
    debugMem("6");

    count++;
    fromIndex = toIndex + 1;
  }
  debug(F("Total phones count:"));
  debugln(count);

  debugMem("7");

  return count;
}

Its output:

>>>>>>>>>>1647<<<<<<<<<<1
Phone index: 0
Substring: 0..10
>>>>>>>>>>1647<<<<<<<<<<2
>>>>>>>>>>1634<<<<<<<<<<3
Phone to add: 1234567890
Phone located at 2273
Array allocated bytes: 6
Array located at 623
>>>>>>>>>>1626<<<<<<<<<<4
>>>>>>>>>>1626<<<<<<<<<<5
Added phone: 1234567890
Added phone located at 623
>>>>>>>>>>1613<<<<<<<<<<6
Phone index: 1
Substring: 11..21
>>>>>>>>>>1626<<<<<<<<<<2
>>>>>>>>>>1613<<<<<<<<<<3
Phone to add: 2345678901
Phone located at 2273
Array allocated bytes: 12
Array located at 657
>>>>>>>>>>1607<<<<<<<<<<4
>>>>>>>>>>1607<<<<<<<<<<5
Added phone: 2345678901
Added phone located at 663
>>>>>>>>>>1607<<<<<<<<<<6
Phone index: 2
Substring: 22..-1
>>>>>>>>>>1620<<<<<<<<<<2
>>>>>>>>>>1607<<<<<<<<<<3
Phone to add: 3456789012
Phone located at 2273
Array allocated bytes: 18
Array located at 657
>>>>>>>>>>1601<<<<<<<<<<4
>>>>>>>>>>1601<<<<<<<<<<5
Added phone: 3456789012
Added phone located at 669
>>>>>>>>>>1601<<<<<<<<<<6
Total phones count:3
>>>>>>>>>>1614<<<<<<<<<<7
loop
Phone0
Phone located at 657
Phone: 1234567890
Phone1
Phone located at 663
Phone: ‚56789012
Phone2
Phone located at 669
Phone: 3456789012

As you can see, while there are no observable fails on filling, it outputs shit in a loop.

I wish to know what the right way to add a String to a (dynamic) array of Strings is. Why does the first version work fine? Is it just because it's so small/simple?

PS1: short version of the first example (w/o debug output):

String* arr;

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

  String src = "abcdefghijk";

  arr = malloc(3 * sizeof(String));

  String dest1 = src;
  String dest2 = src;
  arr[0] = src.substring(0);
  arr[1] = dest1.substring(0);
  arr[2] = dest2.substring(0);

  dest1.setCharAt(1, 'z');
  dest2.setCharAt(1, 'x');
}

PS2: short version of the second example (with min debug output):

String* phones;
int phonesCount;

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

  String reply = "1234567890,2345678901,3456789012";

  fillPhones(reply);
}

void loop() {
  debugln(F("loop"));

  for(int i = 0; i < phonesCount; i++) {
    debug(F("Phone"));
    debugln(i);
    debug(F("Phone located at "));
    debugln((long)(phones + i));
    debug(F("Phone: "));
    debugln(*(phones + i));
  }
}

void fillPhones(String reply) {
  phonesCount = parsePhones(reply, phones);  
}

int parsePhones(String reply, String* &phonesArray) {
  phonesArray = NULL;
  int count = 0;

  int fromIndex = 0;
  int toIndex = 0;
  while(toIndex >= 0) {
    toIndex = reply.indexOf(F(","), fromIndex);

    String entryPhone = reply.substring(fromIndex, toIndex);

    String* tmpArray = realloc(phonesArray, (count + 1) * sizeof(String));
    if(tmpArray == NULL) {
      break;
    }
    phonesArray = tmpArray;

    phonesArray[count] = entryPhone;

    count++;
    fromIndex = toIndex + 1;
  }
  return count;
}
11
  • 4
    1) don't use malloc in a C++ program. 2) learn about std::vector. 3) learn about smart-pointers. 4) if you insist on using (horrible) raw pointers; at least do it correctly and clean up after yourself - your program leaks. Commented Nov 21, 2017 at 18:28
  • 4
    @JesperJuhl I agree with #1, but for #2 and #3, Arduino has very limited resources so raw pointers and arrays are usually used. Commented Nov 21, 2017 at 18:30
  • 1
    @Johnny Mopp In that case 4 definately comes into play.. Also; std::unique_ptr optimizes away completely - it's a 0-overhead abstraction. Besides; if that was all that was wrong with OPs code we'd be golden. But it's not. It's basically bad C made to compile with a C++ compiler. Commented Nov 21, 2017 at 18:32
  • What is String? You haven't showed us its definition/code. Commented Nov 21, 2017 at 18:41
  • 2
    @JesperJuhl There is no std on an Arduino. And String is a Arduino specific class. Commented Nov 21, 2017 at 18:43

2 Answers 2

2

I believe you can do it like this.

String arr[3];
arr[0] = "one";
arr[1] = "two";
arr[2] = "three";

If the array needs to grow in number of elements use std::vector.

#include <vector>  // include this header
// ...

std::vector<String> arr;  // declare the array
arr.push_back("one");  // add as many elements as you want
arr.push_back("two");
arr.push_back("three");
arr[0];  // you can access the elements just like if it was a regular array

EDIT:

Since there are no vectors available. I believe the best option is to allocate a large enough array of pointers to Strings. Then keep an index to the next String to allocate.

const unsigned MAX_PHONES = 1024;
String* arr = new String*[MAX_PHONES];
unsinged arrSize = 0;

// ...

// add new string
if(arrSize < MAX_PHONES)
    arr[arrSize++] = new String;
else
    assert(false && "MAX_PHONES exceeded");


// releasing memory
for(int i=0; i<arrSize; i++) delete arr[i];
delete[] arr;
Sign up to request clarification or add additional context in comments.

4 Comments

Or one could just use std::string rather than raw arrays of char for a much happier life..
@user1754322 I'm asking about MCU program, there is no built-in implementation of vectors and all that stuff.
Sorry, I didn't know that. I updated the answer. Would that work?
@user1754322 Seems I've got an idea, but it looks not applicable for me (mostly due to a very limited RAM size). I wished to use realloc() to simplify extending an array. Also, an option to keep an array of String* and (separated) String instances itself works, but requres more memory and, IMHO, can cause it's fragmentation. If it's the only right way - OK, but I can't understand why does my first code example work.
0

Summarizing results of own investigations and other's remarks - malloc()/realloc() should be avoided in Arduino programs (at least - ones built in Arduino IDE), always use new operator to allocate dynamic arrays (though you'll need to define a custom alternative for realloc() function).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.