Arduino String: C++ style string vs old style c string Compared. Should you use the Arduino String class or old style c strings? Is there a memory saving? Which is easier? Find out here with a Full Serial command decoder example.

Arduino string: What's the difference between an older style C-string and a C++ string?... and should you worry about it?

  • Save 2k Flash if you use C-string syntax.

  • C++ Strings are easier to code...

  • ...but they use dynamic memory,

  • ...so you might run out of memory = hang.

  • learn how to decode serial data into a variable.

You use the Arduino String Class to manage lines of text stored in SRAM and you can control strings in different ways:

  • Initialize them - easily setup text messages.
  • Concatenate them (add characters or append another string).
  • Search through them (identify what is within a string).
  • and more.

They provide the human interface to and from the outside world. So for instance you will use Strings to store, and later send messages to an LCD or to receive and send data through serial link etc.

Note: In this page the word 'String' (in this page) with capital 1st letter refers to the String class (C++) whereas non-capitalized 'string' refers to C style strings. You can use either in the Arduino Compiler (avr compiler).

Learn Arduino String by Example

To learn how to use Arduino Strings lets solve a simple problem, receiving and transmitting data through the serial port.

One problem with compiling C/C++ code is that to change a constant in a program requires re-compilation and this takes time. Using the serial port allows you to update internal variables in real time without recompiling the code.

The code below accepts data from the serial port in the form of text commands that control simple on/off variables. A further section below shows you how to get integer text values into an integer variable while your Arduino program is running.

It's quite an interesting problem, as data from the RS232 interface is "serial" so you have to build up a commands from the serial port byte by byte. You also need to identify when a command is complete and then take action on the command.

The first thing to do, as you do for all serial communications, is initialize the Serial class in the setup() function with the baud rate (a startup message is also useful to show the Code has begun):

void setup (void) {
   Serial.begin(9600);
   Serial.println("Command Interpreter");
}

For this tutorial the operational code will be in the loop function but as you program grows, you will want to create a separate function to contain the serial decode operation. For now lets leave it in loop().

First off you need to declare a variable of type String  to hold the data from the serial port; Here this will be sdata (a String class object).

String sdata="";

As each byte of data comes in you also need a temporary storage area to process it:

byte ch = ' ';
The function needed to decide if a byte has been received is
Serial.available()

Which returns true if anything has been received at the serial port.

Next you need to read the data from the serial port into a variable using the String class member function read():

Serial.read();

This returns a byte of data.

Arduino String Serial Command Decode Structure

With these four bits of code you can make up the serial receiver as follows:

String sdata="";  // Initialised to nothing.

void setup (void) {
   Serial.begin(9600);
   Serial.println("Command Interpreter");
 }

void loop(void ) {
byte ch;

   if (Serial.available()) {
      ch = Serial.read();

      sdata += (char)ch;

      if (ch=='\r') {  // Command recevied and ready.
         sdata.trim();
         // Process command in sdata.


         sdata = ""; // Clear the string ready for the next command.
      }
   }
}

The code in setup() initializes the serial port to 9600 Baud and prints out the first string message.

In loop() the serial port is continuously monitored and if a byte is received it is placed into variable ch and appended to the string sdata. Notice how the  C++ String syntax allows you to use intuitive '+=' operator. This adds one string to the end of another by using the overloaded '+' operator, here using the shortened form '+='.This is not possible with standard c-strings; for that you would use a function strcat (short for string concatenate).

If the byte was a Carriage Return (CR - when you hit the return key), then the trim function is used to remove all white space at the end of sdata. Now sdata is ready to be used in the rest of your program.

Just before leaving the if condition block, sdata is reset to empty, ready for the next command decode.

Arduino String Serial Command Control

Now lets look at the section that you will write after the comment:

// Process command in sdata.
This is where you can decode the serial data which ended in \r (now removed by trim().

The easiest way to do it is to examine the 1st character and use that in a switch statement. To access a character at a specific position see the charAt member of Class String.

sdata.charAt(0)

Here's the switch statement that decodes each command

// Process command in sdata.
   switch( sdata.charAt(0) ) {
   case 's':
      start = 1;
      Serial.println("Start Process");
      break;
   case 't':
      test = 1;
      Serial.println("Test");
      break;
   default: Serial.println(sdata);
   } // switch

Notice how a Serial.println is used to send information back to the user. This is not really necessary but allows you to see that a command has been actioned - making the program easier to debug. The variables start and test are defined earlier in the program.

The above code gives you simple control of variables within a microcontroller.

Arduino String Serial Command Value Entry

To enter a value into a variable, under serial control, you can use the following as part of the switch statement.

   case 'v':
      if (sdata.length()>1){
         valStr = sdata.substring(1);
         val = valStr.toInt();
      }
      Serial.print("Val ");
      Serial.println(val);
      break;

This simply uses the functions substring() and toInt() to retrieve a number string from the serial port and convert it to internal number format.

If the serial data input begins with a v then this section of the switch statement is activated. if there is more data after the 'v' i.e. you typed a number, then valStr is set to a new string that starts from index 1 of sdata to the end i.e. avoiding the 'v' character.

valStr is then decoded from string to int using the toInt() function and the value placed in variable 'val'. 'val' is then printed back to the serial port to show what happened.

If you only typed 'v' then only the last two print statements are executed since the string's length is now one thus printing out the current value of 'val'. In this way you can either change or just query the value of 'val'.

Arduino String Sketch Examples

Full Command Decoder using Arduino String

The following Arduino String Sketch example pulls all of the above code snippets together showing you how to decode serial data for entering values into variables in any Arduino Sketch.

String sdata="";  // Initialised to nothing.
byte test,start;

void setup (void) {
   Serial.begin(9600);
   Serial.println("Command Interpreter");
}

void loop(void ) {
byte ch;
String valStr;
int val;

   if (Serial.available()) {
      ch = Serial.read();

      sdata += (char)ch;

      if (ch=='\r') {  // Command received and ready.
         sdata.trim();

         // Process command in sdata.
         switch( sdata.charAt(0) ) {
         case 's':
            start = 1;
            Serial.println("Start Process");
            break;
         case 't':
            test = 1;
            Serial.println("Test");
            break;
         case 'v':
            if (sdata.length()>1){
               valStr = sdata.substring(1);
               val = valStr.toInt();
            }
            Serial.print("Val ");
            Serial.println(val);
            break;
         default: Serial.println(sdata);
         } // switch

         sdata = ""; // Clear the string ready for the next command.
      } // if \r
   }  // available
}

[file:string.ino]

Statistics for Above String based command decoder (IDE ver 1.8.8):

Sketch uses 3912 bytes (12%) of program storage space. Maximum is 30720 bytes.
Global variables use 248 bytes (12%) of dynamic memory, leaving 1800 bytes for local variables. Maximum is 2048 bytes.

Serial Monitor Results for Command Decoder

Here's what happens when you type 's', 't', 'v198' - after each command hit the return Key (Enter key).

Command Interpreter
Start Process
Test
Val 198

This shows that each section of the switch statement was activated and that the variable val has the decimal value 198. This is true since the code...

Serial.println(val);

...converts the internal binary representation of 'val' into text readable form and sends the data out to the serial port.

Full Command Decoder using c string

Now lets look at the c string version of the same operation (you'll see why in a while!). The code is essentially the same as the previous String code but use 'c' access operations so instead of sdata.charAt you use the array access method sdata[0]. Instead of valStr.toInt() you use the function atoi().

byte test,start;

#define BUF_LEN 20

void setup (void) {
   Serial.begin(9600);
   Serial.println("Command Interpreter");
}

void loop(void ) {
static char sdata[BUF_LEN], *pSdata=sdata;
byte ch;
int val;

   if (Serial.available()) {
      ch = Serial.read();

      // -1 for null terminator space
      if ((pSdata - sdata)>=BUF_LEN-1) {
         pSdata--;
         Serial.print("BUFFER OVERRUN\n");
      }

      *pSdata++ = (char)ch;

      if (ch=='\r') {  // Command received and ready.

         pSdata--;       // Don't add \r to string.
         *pSdata = '\0';  // Null terminate the string.

         // Process command in sdata.
         switch( sdata[0] ) {
         case 's':
            start = 1;
            Serial.println("Start Process");
            break;
         case 't':
            test = 1;
            Serial.println("Test");
            break;
         case 'v':
            if (strlen(sdata)>1)
               val = atoi(&sdata[1]);

            Serial.print("Val ");
            Serial.println(val);
            break;
         default: Serial.println(sdata);
         } // switch

         pSdata = sdata; // Reset pointer to start of string.

      } // if \r
   }  // available
}

Statistics for above Arduino string (c string based) command decoder (IDE ver 1.8.8):

Sketch uses 2076 bytes (6%) of program storage space. Maximum is 30720 bytes.
Global variables use 270 bytes (13%) of dynamic memory, leaving 1778 bytes for local variables. Maximum is 2048 bytes.

Note: The output and ultimate operation of the above program is exactly the same as for the 'String-class' based one.

Arduino String References

Arduino String Objects

For C object Strings a good place to look is the Arduino String Reference. This shows you the available member functions (scroll down for links).

Arduino string (c style strings)

For C object Strings an ok place to look is the Arduino c string Reference.

However the above link does not detail the functions available for c-style strings. For that look to a standard reference. This link is good as it orders the functions in the order of most used.

P.S. Have a look at strtok() as this can allow you to process a command line with multiple commands separated with a delimiter e.g. a semi-colon - Useful for a more complex serial command decoder.

Comparison of String and c string

The following table shows the difference in memory usage:

Type
Flash
SRAM
String
3912 248
c string
2076
270
Difference String cf c string
+1836
-22

Note: It appears that the String class uses less SRAM but it, in fact uses the heap and uses more than c string uses (it is just not easy to measure because it can change all the time as strings are created and destroyed).

Using Class String to control strings is undoubtedly the easiest way and is very similar to higher level languages such as python but it comes at a cost - that cost is Flash program memory and an unknown amount of SRAM!

Flash memory

Although this c-string program performs exactly the same operation as the String based one, the String based one adds a further 1836 bytes of Flash. This is the String class performing magic in the background.

This amount of memory may not matter to a device with large Flash memory but it is fairly significant when using an Arduino Uno (32k Flash).

Arduino String SRAM Use

You can see that the SRAM use for c strings is higher than for String - this is because the buffer is declared as a static array of bytes before the program runs:

static char sdata[BUF_LEN],

This means the SRAM for this string is reserved before run time. When using the heap, (in the String Class) memory is allocated at run time. That really means the compiler does not known how much memory is (or will be) used by the program when using String objects (and it will be larger as there is an overhead in managing blocks of SRAM using malloc and free which are the memory allocation and deallocation functions used in C and C++).

SRAM Memory Size

The other thing to notice is:

  • In the String program there is no memory declaration.

There is no specification of the size of memory used to store the string - in fact the strings are stored in SRAM but they use the heap. The heap is unused memory that sits between the stack and the variable space.

How is SRAM used for String

The String class member functions use SRAM starting from the top end of memory and building down. The blocks of memory are allocated and 'freed' using a cleanup algorithm.

Should You Use String?

If you search on the Web, you will find that there can be problems in using heap based systems and that is due to memory fragmentation. Fragmentation in the heap is caused when strings of larger length can not be allocated to a 'freed' memory block (since the block that had been previously 'freed' up is too small); they have to be allocated in new memory. This leaves lots of small blocks of unused memory.

In the extreme case memory fragmentation, caused when you have low SRAM! and lots of string manipulations, can cause your program to hang - the solution is to press the reset button! For small programs with low numbers of Strings, its fine (there won't be #enough memory fragmentation to cause the program to hang.

The following statement is from a consortium of car manufacturers MISRA (Motor Industry Software Reliability Association). They specifically forbid using heap based memory management because safety is crucial:

    MISRA C++ rule 18-4-1, dynamic heap memory allocation cannot be used.

This document describes static, dynamic memory fragmentation etc. but its final conclusion is this:

"Exhaustion is still our major impediment to using dynamic memory in real-time embedded systems. A good failure policy based around std::bad_alloc can address many of the issues, but in high integrity systems dynamic memory usage will remain unacceptable."

For programs that do not use many strings the Arduino string class is fine. However if you have low levels of SRAM and use lots of string manipulations you could run into fragmentation problems (random failure).

What exactly is a String?

As noted at the start, a string is a line of text stored in SRAM.

Note: This section applies to both Strings and c-strings.

Both Strings and 'strings' are dynamic entities meaning they can be changed whenever you want since they exist in SRAM. For an embedded system that typically has very low SRAM available (Arduino Uno 2k Byte) it means the SRAM will disappear fast unless you put all your constant strings into Flash.

In fact strings are doubly wasteful in C or C++, because they use both Flash and SRAM.

The first reason is initialisation. You probably want to start off a string with some information in it e.g. "Initialising I2C" etc.; Sometimes you don't e.g. a receive buffer for a serial input. To do the initialisation you need somewhere to store the string when the power is off, and that store is Flash memory.

The second reason is that strings are defined as existing in RAM so that they can be changed as your program runs.

In some cases you don't want an updatable string e.g for a text message that never changes. The problem is that the compiler won't know that the string is never going to change.

Putting Strings into Flash memory

To place strings into Flash, in Arduino code, enclose a fixed string with the F macro e.g. Serial.print(F("My fixed string")); That leaves the string in Flash memory.

Using the F() macro stops the transfer of initialization data from flash memory to SRAM and only uses the data from the Flash memory, so you save SRAM.

TIP: There are other functions specially made for using Flash memory e.g. string copy from Flash to 'a normal string in SRAM'. You can find these by searching for "Arduino PROGMEM strcpy_P". These are specialized functions that you won't need often but it is good to know they are available.


Arduino Strings and c strings compared

Advantages of String class

  • Does not allow buffer overrun.
  • Easy to use and intuitive e.g. to append strings use the '+ operator

Disadvantages of the String class

  • Uses more Flash memory.
  • Uses the heap leading to heap fragmentation.
  • Uses the heap that uses an undefined amount of SRAM at run time.

Advantages of c strings

  • Uses minimum Flash memory.
  • Does not use the heap.
  • You have complete control of how memory is used.

Disadvantages of c strings

  • Not intuitive.
  • Need to learn slightly cryptic functions to use them.
  • Can get buffer overrun if not careful.

Conclusions

The basic truth is this:

  • String Class objects are easier.
  • String Class objects can cause failure due to memory fragmentation.

So if your primary concern is operational safety (or of lesser importance - reliability), don't use String class objects - use c-style code.



Comments

Have your say about what you just read! Leave me a comment in the box below.

Don’t see the comments box? Log in to your Facebook account, give Facebook consent, then return to this page and refresh it.




Privacy Policy | Contact | About Me

Site Map | Terms of Use