Dealing with Strings


#1

I am wondering if there are any benefits to dealing with strings like the 1st way vs the 2nd way? The 1st way uses slightly less memory.

      char eventString1[100];
      snprintf(eventString1, sizeof(eventString1),
        "%u%s",
        eventCt,
        ": "
      );
      for (byte i = 0; i <= eventCt; i++) {
        strcat(eventString1, String(convertTimeToUTC(dataArr[i][0])));
        strcat(eventString1, ",");
        strcat(eventString1, String(convertTimeToUTC(dataArr[i][1])));
        strcat(eventString1, ",");
        strcat(eventString1, String(convertTimeToUTC(dataArr[i][2])));
        strcat(eventString1, ",");
        strcat(eventString1, String(convertTimeToUTC(dataArr[i][3])));
        strcat(eventString1, "-");
      }

      snprintf(eventString1, sizeof(eventString1),
        "%s%s%s",
        eventString1,
        ":",
        System.version().c_str()
      );

      if (Particle.connected()) {
        Particle.publish("test", eventString1, PRIVATE);
      }

OR

      String eventString1 = String(String(eventCt) + ": ");
      for (byte i = 0; i <= eventCt; i++) {
      eventString1.concat(String(convertTimeToUTC(dataArr[i][0])) + "," + String(convertTimeToUTC(dataArr[i][1])) + "," + String(convertTimeToUTC(dataArr[i][2])) + "," + String(convertTimeToUTC(dataArr[i][3])) + "-");
       }

  
      if (Particle.connected()) {
        Particle.publish("test", String(eventString1 + ":" + String(System.version())), PRIVATE);
      }

#2

The second version is not advisable due to the dynamic memory allocation of ‘String’ which - over time - may lead to heap fragmentation (this is also the case for the first way, but less excessive due to fewer String objects being created in the process).

There also is another version that’s even shorter to write

      char eventString1[100];
      snprintf(eventString1, sizeof(eventString1), "%u: ", eventCt);
      for (byte i = 0; i <= eventCt; i++) {
        snprintf(eventString1, sizeof(eventString1)
                , "%s%d,%d,%d,%d%s"
                , eventString1
                , convertTimeToUTC(dataArr[i][0])
                , convertTimeToUTC(dataArr[i][1])
                , convertTimeToUTC(dataArr[i][2])
                , convertTimeToUTC(dataArr[i][3])
                , i < eventCt ? "-" : ":"
                );
      }
      strncat(eventString1, (const char*)System.version(), sizeof(eventString1));

(assuming convertTimeToUTC() returns an int - otherwise just replace %d with the correct place holder)

The code above renders a slightly different string than yours, since it omits the final hyphen before the colon introducing the version number.
If you could accept swapping the last colon for another hyphen you could even do away with that strncat() when using this instead

        snprintf(eventString1, sizeof(eventString1)
                , "%s%d,%d,%d,%d-%s"
                , eventString1
                , convertTimeToUTC(dataArr[i][0])
                , convertTimeToUTC(dataArr[i][1])
                , convertTimeToUTC(dataArr[i][2])
                , convertTimeToUTC(dataArr[i][3])
                , i < eventCt ? "" : (const char*)System.version()
                );

You could also have a nested loop for the four fields of dataArr[i][0..3].

In general: It’s always advisable not to use String at all (if possible).


#3

Hi ScruffR,

Just to jump on to this question, would it be permissible to use std::strings if they’re declared const? I basically need to store some warning messages and pass them around by reference… I’m also wondering if const std::array would be permissible, or if it would fragment.


#4

Awesome, thanks! I have a ton of strings right now in other parts of the code and this will help. FYI I had to add the below to get this to work correctly.

snprintf(eventString1, sizeof(eventString1)
               , "%s%d,%d,%d,%d-%s"
               , eventString1 <-------- HAD TO ADD THIS TO GET IT WORKING
               , convertTimeToUTC(dataArr[i][0])
               , convertTimeToUTC(dataArr[i][1])
               , convertTimeToUTC(dataArr[i][2])
               , convertTimeToUTC(dataArr[i][3])
               , i < eventCt ? "" : (const char*)System.version()
               );

#5

It wouldn’t change a lot since you would only declare the object itself const but its data “segment” wouldn’t be. Data management will always be dynamic.
You can construct a String (and for that std::string and std::array too) with a big enough initial size to avoid relocation, but most issues don’t come from the original variable but from all the temporary objects that get created when building string like above.

Sorry, that was an oversight on my part :blush: - I corrected my code above.


#6

Ah, interesting. I’m basically storing data (pixel bits for OLED, or a string warning message) so they’re created once and then sent to the OLED or SD card log, so I might be okay.


#7

In principle yes, but for that you’d better use const char[] or const char* anyhow since then you won’t need to copy the string from flash into the String object but directly take if from flash.

Any initialised but non-const string will take at least twice its size in memory (1x flash for the initial value + 1x in RAM + the instructions and time needed to copy the data).


#8

Thanks very much for the info. Sorry to keep poking at it, just trying to get a handle on what’s going on. I guess I should have mentioned that I can declare them as const. Down the road I’d like to store the various images or messages in the extended flash on the mesh family, but I’m not sure when the API for that will be available.


#9
const String s = "some String";

Won’t give you the same benefits, since that only makes the object itself const but not it’s non-static members and the string buffer has to be non-static by definition.


#10

Ah, gotcha, thanks. Guess I’ll try to get it working with c-style arrays and strings and a few pointers.


#11

@psherk, there is no API for using the external mesh flash. This is something I am pushing for in a future release.


#12

Hi peekay. I really hope the future access happens, as the 4MB external flash was a pretty big selling point. Thanks for pushing for it!


#13

@psherk, the flash is currently half used so only 2MB would be available in the future.