I use strncpy() a d strlcpy() without including anything (besides the automatically included Arduino.h)
No need to get bloated software with the SafeString library, it does not bring much to the table as you need to test the result of the operations after trying to know if you had a buffer issue, which basically is the same thing you do if you are a carful programmer with the standard functions - sometimes before calling the function instead of after.
That is you would do
If there is enough space in the buffer then DothisBufferThing
else handle error
Whilst with the library you do
If DothisBufferThing is an error then handle error
So my recommendation is to learn and use the standard functions throughout, that’s knowledge you can apply much more generally and it will serve you well during all your programming efforts.
As a general principle it’s better to know you will be safe before trying rather than trying to fix things after attempting to do them.
Like
If the parachute is OK then jump off the plane, enjoy the view and the free fall and land safely otherwise don’t even board the plane
Is better than
Jump off the plane If the parachute does not open then pray… otherwise enjoy the view and the free fall and land safely
There are cases where you can’t and your code need to be ready for that (like parse an input string you don’t know ahead of time but there are standard functions (that the library uses)) to handle those cases.
The "worst" thing that can happen with a SafeString is:
A string that has more characters than the SafeString can store get's truncated.
result: A string does not match what the string was intented to be.
So some if-condition will not evaluate to true or some text shown on a display or sent to a database is truncated.
A c_string that get's assigned more characters than the c_string array was defined for will overwrite other RAM. Which leads to absolutely unpredictable results.
And there you are right for using c_strings you must kow very well what you are doing
And you should add this information to your recommendation at first place.
For the "bloating" of the SafeString-library
post a comparising how many bytes using the SafeString-library are required
compared to
a c_string code that includes all the self written safety-checking code for a medium complex case
I 'm pretty sure as soon as all the safety-checking code is added the difference how many bytes are required will be small.
proof me wrong by posting a demo-code
Say you want to send the message Do not give him the medicine if he has already taken it
but there's not enough space in the buffer and you send Do not give him the medicine
or Take the patient off the ventilator once spontaneous breathing resumes
but there's not enough space in the buffer and you send Take the patient off the ventilator
Now your patient is dead. What do you do ? is that your suggestion?
if (patientIsDead()) {
callTheLawyer();
apologize();
getYourWalletOut();
fireTheSoftwareEngineer();
goToJail();
}
So no — I'm not sure I would call this "limping".
When you code, you need to check out for either pre-condition are OK for the operation you intend to do (don't divide by 0, don't pass the car if there is someone coming in in other direction or already passing you, ....) or in some cases, when you can't do otherwise, test for success.
Biut in any case, you can't just trust everything was fine unless you can prove it's going to always be fine (eg no need to check if an unsigned int is negative)
That's my point, check first or use the n or l version of the c-string function as OP wanted to do.
Trying to do some substraction with unsigned int can result in "very different" values than you "normally" expected. So again you have to know very well what you are doing.
can result in the same truncation as using SafeStrings
To only use SafeStrings or any other microcontroller in hobby projects as undangerous as
"show outside temperature" or similar.
A life-critical device or life-critical communication like in your example posted above is indeed something for very experienced professionals that really know how to use c_strings and how to make the use of c_strings as safe as possible.
I did not mention subtraction, I mentioned testing for not negative.
OK, I'll bite and I'll meet you half way.
As I'm no pro with the SafeString library, please write something that does the exact same thing as I'm doing here with the SafeString library.
void test(const char * someText) {
constexpr size_t maxSize = 10;
char cStr[maxSize];
long longNumber;
Serial.print(F("-----------------------\nTesting with: ["));
Serial.print(someText);
Serial.println(F("]"));
if (strlcpy(cStr, someText, maxSize) < maxSize) {
char* endPtr;
longNumber = strtol(cStr, &endPtr, 10);
if (endPtr != cStr) {
Serial.print(F("longNumber set to ")); Serial.println(longNumber);
if (*endPtr != '\0') {
while (isspace(*endPtr)) endPtr++;
if (*endPtr == '\0') {
Serial.println(F("only spaces remained after the number."));
} else {
Serial.print(F("Unexpected characters remain: '"));
Serial.print(endPtr);
Serial.println(F("'"));
Serial.println(F("Not a pure valid integer in input."));
}
}
}
} else {
Serial.println("Buffer too small");
}
}
void setup() {
Serial.begin(115200); Serial.println();
test(" -42 xyz");
test("-42 \t\r\n");
test("-42");
test("-42 and a very long text that does not fit");
}
void loop() {}
This will print out
-----------------------
Testing with: [ -42 xyz]
longNumber set to -42
Unexpected characters remain: 'xyz'
Not a pure valid integer in input.
-----------------------
Testing with: [-42
]
longNumber set to -42
only spaces remained after the number.
-----------------------
Testing with: [-42]
longNumber set to -42
-----------------------
Testing with: [-42 and a very long text that does not fit]
Buffer too small
Your code needs to detect the same situations and print the exact same error messages so that flash memory usage is comparable.
On a UNO my code is using 3130 bytes of Flash and 270 bytes of RAM.
to get you started, but may be it's not the best implementation possible
you can start from this
#include "SafeString.h"
void test(const char * someText) {
constexpr size_t maxSize = 10;
createSafeString(safeStr, maxSize);
long longNumber;
Serial.print(F("-----------------------\nTesting with: ["));
Serial.print(someText);
Serial.println(F("]"));
safeStr = someText;
if (not safeStr.hasError()) {
if (safeStr.toLong(longNumber)) {
Serial.print(F("longNumber set to "));
Serial.println(longNumber);
} else {
Serial.println(F("Not a pure valid integer in input."));
}
} else {
Serial.println("Buffer too small");
}
}
void setup() {
Serial.begin(115200); Serial.println();
test(" -42 xyz");
test("-42 \t\r\n");
test("-42");
test("-42 and a very long text that does not fit");
}
void loop() {}
You need to add detection of the situation when only spaces where left after the number and print that
Serial.println(F("only spaces remained after the number."));
or if after the spaces there were some other text, then print that too
Serial.print(F("Unexpected characters remain: '"));
Serial.print(...); // here print the remaining characters after the spaces
Serial.println(F("'"));
Serial.println(F("Not a pure valid integer in input."));
I'm already at 5160 bytes of flash (a 64% increase) and 378 bytes of RAM (a 40% increase) and I don't even have all the algorithm and the error messages in there.
to avoid any data allocation on the stack, we could also try with the buffers allocated at compile time
constexpr size_t maxSize = 10;
char cStr[maxSize];
long longNumber;
void test(const char * someText) {
Serial.print(F("-----------------------\nTesting with: ["));
Serial.print(someText);
Serial.println(F("]"));
if (strlcpy(cStr, someText, maxSize) < maxSize) {
char* endPtr;
longNumber = strtol(cStr, &endPtr, 10);
if (endPtr != cStr) {
Serial.print(F("longNumber set to ")); Serial.println(longNumber);
if (*endPtr != '\0') {
while (isspace(*endPtr)) endPtr++;
if (*endPtr == '\0') {
Serial.println(F("only spaces remained after the number."));
} else {
Serial.print(F("Unexpected characters remain: '"));
Serial.print(endPtr);
Serial.println(F("'"));
Serial.println(F("Not a pure valid integer in input."));
}
}
}
} else {
Serial.println("Buffer too small");
}
}
void setup() {
Serial.begin(115200); Serial.println();
test(" -42 xyz");
test("-42 \t\r\n");
test("-42");
test("-42 and a very long text that does not fit");
}
void loop() {}
➜ Flash = 3146 bytes, RAM =284
and the start of the SafeString code that needs more code and text messages
#include "SafeString.h"
constexpr size_t maxSize = 10;
createSafeString(safeStr, maxSize);
long longNumber;
void test(const char * someText) {
Serial.print(F("-----------------------\nTesting with: ["));
Serial.print(someText);
Serial.println(F("]"));
safeStr = someText;
if (not safeStr.hasError()) {
if (safeStr.toLong(longNumber)) {
Serial.print(F("longNumber set to "));
Serial.println(longNumber);
} else {
Serial.println(F("Not a pure valid integer in input."));
}
} else {
Serial.println("Buffer too small");
}
}
void setup() {
Serial.begin(115200); Serial.println();
test(" -42 xyz");
test("-42 \t\r\n");
test("-42");
test("-42 and a very long text that does not fit");
}
void loop() {}
I think the main thing to keep in mind is that safeString was meant to be a replacement for the String class, but to prevent memory fragmentation, and an easier / safer use for c-strings,
An experienced programmer will nearly always be better of using either of the other 2 methods.
There is most likely going to be overhead with these safeStrings in some way or another.
Using the String class or using char-arrays is pretty much always going to be more efficient, and from an educational point of view, there is understanding to be gained from actually 'knowing' what is goin8g on under the hood with Strings and how a char-array works. That said, if someone wants to write a simple program but not delve into these complications of memory management, safeString can be a simple solution, that will prevent the program from breaking.
In my opinion though it does not fully meet the requirements i would put on that. Personally i would be more in favor of a 'heap-managing' system that prevents fragmentation but still uses the heap for expandable Strings, rather than a system which will just prevent writing beyond the bounds of the array. Problem with heap management is that the String class is not the only thing that can use the heap, and to manage the heap, it needs to be cleared first and temporarily stored, and then reconstructed. The methods for that would seem rather complex and will most likely take up quite a bit of flash programming space. In the end, being able to use the standard methods is the most efficient regardless, and the best way to become a better programmer.
My view is that if you want. to write a simple program, then you can use the String class and be done with it.
If you want something more complex with proper fault testing in your code — not crashing is not good enough, you need to know things worked as expected (otherwise I would argue a crash is better as you know you have a bug ), then there are already functions available to do right that and not surprisingly this is what the SafeString library is using under the hood.
SafeString's interest is possibly when you master all this and don't want to bother with minute details of the implementation using the standard functions and are willing to pay the extra cost in RAM / Flash ➜ for the lazy programers
People who write code that lets there be data that goes out of bounds need error handling. People who vet their data don't go out of bounds, but that takes planning and work.
As for me, I spent years using string.h and know the functions well. When I want better, I roll my own.
Arrays, pointers and arranging, engineer your needs.
Without a detailed explanation of your c_string code I am simply too lazy to analyse it
So you can call me a lazy flash and RAM-wasting programmer. Is OK for me
the goal is to write a function that takes a cString as an input and try to extract a long integer from it and reports findings:
first I copy the string into a buffer. If the input fits within the buffer, it proceeds; otherwise, it reports "Buffer too small".
Then I want to convert the content of the buffer into a long and report
if it could extract a number
if there were only spaces after the number
if there were something else after the number and in that case print it
In essence, the function safely parses an integer from a cstring, ensuring the buffer is not overflowed and detecting any extraneous characters after the number.
that's not the point. You code the way you want.
There was a technical discussion and you challenged me with your view
I gave you a code with cStrings and a code that does not even do as much with the safeString that shows a 64% increase in flash memory and a 40% increase in RAM usage. As you are the expert with SafeString here, I'm willing to agree that my code with the SafeString is not good and so it's up to you to write a good code doing the same thing and report on flash and ram usage.
That was YOUR ask and now you are taking the coward way out... I'm disappointed.
I start (first did this kind of thing with user key entry routines, vetting data) with an accumulator integer that in this case would be a long integer.
Then char by char, with non-digits as exceptions (where sign, commas and dec pt when allowed are exceptions as are errors... I won't go into details here) multiply the accumulated value by radix (10) then add digit char - '0'.
With the delimiter exception, I have the value.
My buffer is the accumulator.
Every new char gets limit checked as to number of digits, another error condition to check.
With ANSI terminal type screens, errors halted entry with a beep, the offending char wiped and entry picks back up from there because why I started doing that was a hatred of filling and entry with one little error making a do-over required. Where I worked, I was the alpha and beta tester and I had to use what I wrote besides (learned much that way, wore more hats too) and I am one lazy bastard!
Buffers.... not if I can help it though very often I use them.
In my first years of coding, half or more bugs I ran across were dure to bad data. I learned to vet my data and spent less time debugging.
If the input is a string array, I iterate through those chars and they had all better be "legal". The input is the buffer.
The base process of * 10 + digit holds.
I ceased using atoi kind of functions soon after not using scanf kind of functions only I started with Basic.
Talking about strings here seems always to be a hot topic. So let me also share my opinion. There is a reason why strings were introduced in C++. They solved buffer overflow bugs which were and are still common also among skilled programmers. They also simplified string calculations since you didn't have to test the success of the result of each calculation any more. With try-catch functionality you can do it only once. Beside all this they are also more efficient since they store string length information internally so concatenation is much faster than with arrays of chars. You can use std::strings with Arduino or use Arduino String data type which is quite similar so I don't really know why we are actually dealing with two very similar data types.
In Arduino std::strings and Strings have a common problem and this is not memory fragmentation which can easily be avoided with a little attention (like avoiding long-lived strings or reserving enough memory in advance). The problem is error handling when running out of memory for example. Since Arduino does not support try-catch functionality, std::strings throw unhandled exceptions causing a restart of the controller (which is not OK) whereas Arduino Strings simply ignore the error (which is also not OK).
My opinion is that if Arduino Strings would keep error information internally so a program would always be able to check it, say once after 100 or more calculations, this would solve all the problems without needing try-catch functionality.
Arduino Strings already have something similar. If String assignment fails, like
String s = "... long string";
you can test the success of assignment with
if (s) ...
Unfortunately this information is not preserved through many calculations like
String s = "... long string" + "short string" + "another string";
so basically you can never be 100% sure what you are getting out of this.
If you have loads of RAM then yes to the first part.
I'm not sure what you call skilled programmers, just fyi.
In small memory environments, String class is a poor choice for reasons posted years ago. You want them, search the archives since I'm not spending time retyping not just my old posts but those of others.