Going nearly crazy

Hi there,
I'm working on an Arduino project for an Arduino Uno. The projects consists of several classes with different functions.
I'm nearly going crazy creating a simple string out of an char[] within a method (function). Whatever I try, nothing works. I tried the same thing in a simple .ino where everything is ok.

So here is what I have:

Extract from Commandinterpreter.h
Here I declare a struct "CommandLineQueueRecord" and a queue "CommandLineInputQueue" which is used as a record to a queue (Library cppQueue)

#include "Arduino.h"
#include "GlobalDefinitions.h"            // Definitions of pins and global definitions
#include "cppQueue.h"

#define MaxCommandLineLenght 16
#define MaxQueueSizeForCommands 2
#define MaxQueueSizeForCharacters 300

class CommandInterpreter {
private:
    typedef struct SerialCharacterQueueRecord {
        char ch;
    };

    typedef struct CommandLineQueueRecord {
        char commandLine[MaxCommandLineLenght];
    };

    static CommandInterpreter* instance;   // Singleton instance;
    static int numberOfNewLinesCharacterQueue;
    static cppQueue* CommandLineInputQueue; 
    static cppQueue* SerialCharacterInputQueue;

Furthermore, in CommandInterpreter.cpp, there's a function "getCommandLine()" that basically should retrieve one commandline from the queue.
I can retrieve a record from the queue and I can also prove, that the content is correct, but I simply fail to put the char[] from the retrieved record to a String. Here's the code of the function:

// ---------------------------------------------
// Tries to retrieve a new command
// ---------------------------------------------
String CommandInterpreter::getCommandLine() {
    CommandLineQueueRecord commandRec;
    char ch;
    String s1;
    String s2;

    if (CommandLineInputQueue->getCount() > 0) {
        CommandLineInputQueue->pop(&commandRec);
        Serial.print("cmdLine=");
        Serial.println(commandRec.commandLine);

        s1 = String(commandRec.commandLine);
        Serial.print("s1=");
        Serial.println(s1);

        // Copy the commandline into a string. All the variants found on the internet do not work! Grrrrrrrrr :(
        // Like: String s = String(commandRec.commandLine);
        // So I will try it manually ==> Also no success

        byte idx = 0;
        s2 = "";
        while (commandRec.commandLine[idx] != 0 && idx <= MaxCommandLineLenght) {
            ch = commandRec.commandLine[idx];
            Serial.print(". ");
            Serial.println(ch);

            s2 = s2 + ch;

            Serial.print("s2=");
            Serial.println(s2);

            idx++;
        }
    }
    else {
        s1 = "";
    }

    return s1;
}

When I run it, we can see, that the content of the record from the code is correct, here "abcd", but then, all attempts to copy it into a string fail, whatever I try:

Output of the console:

cmdLine=abcd
s1=
. a
s2=
. b
s2=
. c
s2=
. d
s2=

Does anybody has a clue, what's going on? Grrr, I am going crazy

Thanks a lot in advance
Michael

In the getCommandLine() function you seem to be building String s2 by adding characters to it

s2 = s2 + ch;

but then you return String s1 from the function

return s1;

Why use Strings? Why not use c-Strings?

Avoid using String objects with AVR processors like the Uno. They are never necessary and furthermore, lead to memory problems and program crashes.

Stick with C-strings (zero terminated character arrays) and the associated helper functions like strcpy, strcat, etc. (better, use the n versions, strncpy, strncat).

1 Like

Look at the console output

Strings are more convenient

Is this proven or just your opinion? Strings are just much more convenient that the old fashioned C-Strings, aren't they?

Every day forum members encounter beginner posts like yours.

1 Like

For any other dev board yes, but the arduino does not handle Strings properly. You also need to reserve space for Strings, so s2 = ""; is only big enough for a single character.

Ah, thank you, I had no idea! Probably I really should consider going the old, annoying way :slight_smile:

Do you mean "all" Arduino boards or just the Arduino Uno?

I cannot say for the newer boards as I have not used them, but Uno and Mega yes.

Ease of use and flexibility...

The "n"
https://cplusplus.com/reference/cstring/strncpy/

Non-"n" (possible buffer overflow)
https://cplusplus.com/reference/cstring/strcpy/

Thank you all.

I didn't know all that but indeed had difficulties in the past with unstable programs. Now I understand, why memory management isn't that easy with only a few bytes of memory. As a experienced C# software developer I just thought, that nowadays String handling shouldn't be such a problem anymore and that compilers etc. would handle this correctly.
What I will do know is looking for my old C++ Books in the shelf and refactor my Arduino programs from String to the old fashioned C-Strings.

Again, thanks a lot for all the links and help
Michael

1 Like

Perhaps this could be of some help: GitHub - BojanJurca/Fixed-size-strings-for-Arduino: C++ fixed size strings (for Arduino)

C style strings residing on the stack but with C++ expressions. They can not be expanded over predefined size though. But they don't mess with the heap at all.

Not yet another "solution" to problems with Strings in the Arduino environment !

Just learn to use C style strings and get on with programming

2 Likes

This "just" is to learn a big heap about the traps of out of boundary writing

which means do a lot of boundary checking to really make sure that you do not crash your code

The more convenient way to use "c_strings" without all the make sure you only store withing boundaries is to use SafeStrings

// SafeStrings are based on array of chars
// SafeStrings offer almost the same comfort as Strings
// but avoid some dis-advantages of variable-type String
// the name SafeString is PROGRAM They are safe to use

// with the alternatives "String" must not always but CAN 
// eat up memory over time which will make the code crash

// with zero-terminated array of chars (c_string)
// you have to take care of boundary checking yourself 
// otherwise your code will start to behave weird and this kind of bug is very hard to find
// you can read more about this here https://www.forward.com.au/pfod/ArduinoProgramming/ArduinoStrings/index.html#safestring
// and here https://hackingmajenkoblog.wordpress.com/2016/02/04/the-evils-of-arduino-strings/

// very basic demo-code to show how to declare a SafeString-variable
// and how to assign text to them

// as a personal convention I use the suffix "_SS" to indicate 
// THIS IS A SAFESTRING-VARIABLE
// but you can name it whatever you like

#include "SafeString.h"
createSafeString(myTitle_SS, 64);  // reserve 64 bytes for the SafeString-variable
createSafeString(myString_SS, 32); // reserve 32 bytes for the SafeString-variable

int myInteger = -1234;
float myFloat = -987.009;


void setup() {
  Serial.begin(115200);
  Serial.println( F("Setup-Start") );
  Serial.println();

  myString_SS = F("fixed text directly assigned");
  Serial.print( F(" #") ); // leading double-cross "#" to show where the string starts
  Serial.print(myString_SS); 
  Serial.println(F("#") ); // trailing double-cross "#" to show where the string REALLY ends


  myTitle_SS = F("content of an integer:");
  myString_SS = myInteger;

  Serial.println(myTitle_SS);
  Serial.print( F(" #" )); // leading double-cross "#" to show where the string starts
  Serial.print(myString_SS); 
  Serial.println( F("#") ); // trailing double-cross "#" to show where the string REALLY ends



  myTitle_SS = F("content of a float:");
  myString_SS = myFloat;

  Serial.println(myTitle_SS);
  Serial.print( F(" #" )); // leading double-cross "#" to show where the string starts
  Serial.print(myString_SS); 
  Serial.println( F("#") ); // trailing double-cross "#" to show where the string REALLY ends

  myTitle_SS = F("you can append more text with the +=-operator ");
  myString_SS = F("text ");
  myString_SS += myInteger;
  myString_SS += F(" ,");
  myString_SS += myFloat;

  Serial.println(myTitle_SS);
  Serial.print( F(" #" )); // leading double-cross "#" to show where the string starts
  Serial.print(myString_SS); 
  Serial.println( F("#") ); // trailing double-cross "#" to show where the string REALLY ends  
}


void loop() {

}

best regards Stefan

Of course. This is just my opinion, I do like C style strings a lot, since you can always tell what is going on with them. But it is very easy to overrun the buffer if you don't pay enough attention and these kinds of bugs are rather difficult to find. Besides, they are inconvenient to use with complicated string expressions.

There are also some tricks that you can use with std::strings or Arduino Strings to reduce heap fragmentation. Like avoiding long living strings with a lot of changes, reserving buffer memory in advance, ....

I also prefer using concat member function, which actually check the success rather than += operator or even + itself (like s = s + "a"). So your code knows when it can not perform the calculation.

Well indeed they won't overflow but if they were to overflow in the regular c-string based code, they would as well there ➜ probably meaning that your code won't do what you want.
do even if you use the so called SafeStrings, you do need to test for "a lot of boundary checking" anyway, just using a different API.

What do you think is better in terms of coding :

  • try to perform an operation and test to see if it failed
  • check if the operation is possible and if so do it

also if you plan to do anything with C/C++ programming for microcontrollers, understanding how to manage arrays and bounds in general (not just for c-strings) is really something you need to know about. So I'd recommend to start there the journey.

if you want a throw away code for a school assignment and it has to run for 3 minutes during your show and tell, then the String class is likely all you need — no need to explore the complexities of the so called SafeStrings API...

I don't know because I do not understand what you want to say with that.

The maximum that can happen with a SafeString is that assigning a string with too many characters gets truncated
Which of course can cause a malfunction of the code

But it will be much easier to debug by printing the variables content

than chasing down the field of rabbit-holes because writing to an array of char outside of its boundaries which can cause ANY kind of strange behaviour you can not predict.