Strings are Evil

The String class, not the char arrays, but that ones declared as |String str = "some text"|, are really poisonous to an Arduino program.

You probably gonna think something like "But they are so nice, I don't need to explicitly allocate them, I can concatenate them like I do in Java or Javascript, ..."

I doesn't matter how many good reasons you have to use, please, just don't. Get rid of them!!!

Why?
Let me explain:

Each declared String variable, when the Arduino starts the program, instantiates an object that, in order to perform its "magic", pre-allocates a lot of memory on the SRAM, even for the smallest strings.

If you are using an Arduino Galileo, which has about 512Kbytes of SRAM, this is not really a problem. But in my case, and probably yours, if you are using a more popular device, like Arduino UNO, you have just 2Kb (2048 bytes) to handle with.

Using the Strings, the Project Io program used all the 2Kb, and restared the Arduino everytime I connected via bluetooth. After I removed the Strings, and replaced them for progr_char[] PROGMEM arrays (see below), the same program, now uses only 570 bytes and runs smoothly.


Possible Solutions



char* / chr[]

Use char arrays for you constants and char pointers for dynamic strings. They are still stored on the SRAM when the program is loaded, but they use less memory than Strings (only the amount of chars).

prog_char[] PROGMEM

For constant strings, this is the preferred way, because it lets the strings to be stored on the program memory (Arduino UNO has 32Kbytes of memory for program).

Of course, there is a catch: You cannot just access the strings in the program memory. First, you need to allocate a char[] our a char*, as a buffer, in the SRAM. After, you load the string body into the buffer and use it to perform whatever you want.

For example, you can declare some strings and a function to load them:
char pmString[20];

prog_char pms_0[] PROGMEM = "String 1";
prog_char pms_1[] PROGMEM = "Any String 2";
prog_char pms_2[] PROGMEM = "Other String 2"

PROGMEM const char *pms_table[] = {pms_0, pms_1, pms_2};

char* getPMString(int pos) {
    strcpy_P(pmString, (char*)pgm_read_word(&(pms_table[pos])));
    return pmString;
}

const unsigned byte st1     = 0;
const unsigned byte anySt   = 1;
const unsigned byte otherSt = 2;



///// somewhere in your program
Serial.println(getPMString(anySt));


Note that:
  • It's necessary to declare each char[] to each string and declare an array containing all the strings.
  • It is necessary to have a char buffer, whose size must match the biggest string you stored on the program memory.
  • The function loads any of the string into the same static buffer (in this case), so it's not possible to load 2 separated strings before use them.
  • If you need to load more than one string before use them, you nedd either to change the getPMString() method to return an allocated char*, or to copy the content of the char buffer to other char*. In both cases, you should be very careful to release the memory after use the strings, otherwise you gonna get out of memory.
  • It's advisable to keep some integer constants to make easier to refer to the strings when loading them. You can refer them by their indexes, but it is not a very good practice.

No comments:

Post a Comment