Arduino deserves reliable 'atoi' and related methods

Arduino has a bespoke version of String that provides reliable replacements for most of the error prone C-string methods. Unfortunately the String class just re-uses C-string's un-reliable atol and related methods.

The failings of atoi / atol ect. well known and often ignored, returns 0 for invalid input and garbage for input that overflows. See the test output below.

This proposal to provides a reliable replacement, a new String method, the number of args destinguishes it from the existing toInt()

  // result unchanged on error
bool String:toInt(const char* a, int& result) {
  bool rtn = false;
  String s = a; // no memory fragmentation as internal String is discarded/reclaimed on method exit
  // use SafeString method here for simplicity  see the SafeString library for the actual code needed
  cSF(sfStr, 15);
  sfStr.readFrom(a); // read at most 15 chars, 
  rtn = sfStr.toInt(result);
  return rtn; 
}

Output from test sketch

 String.toInt("123a") = 123
    New toInt("123a", result) = false

 String.toInt("12345678901") = 7221
    New toInt("12345678901", result) = false

 String.toInt("12345678901234567890") = 2770
    New toInt("12345678901234567890", result) = false

 String.toInt("abc") = 0
    New toInt("abc", result) = false

 String.toInt("1.55") = 1
    New toInt("1.55", result) = false

 String.toInt("5522") = 5522
    New toInt("5522", result) = true
    result = 5522

Why am I raising this long standing issue again now?
I was prompted by the recent report of a conversion function that returned an 'error' value that was in the range of the calling program. Just like returning 0 for toInt(“abc”)
https: // sshhole xxx .com /
try this link OpenSSH flaw lets attackers log into password-disabled Linux accounts

In the sshhole case, since 1980, a password of just * meant no password set for this login. i.e. you cannot login. * is not a valid hash, but 'recent' (2012) code change decided to 'fix' a NULL return used for a hash error return, (that lots of programmers did not check for) by returning * instead. So now if you overflow the password input length you can login to any account with no password set.

Now returning 0 from toInt() for an input of 'abc' is, probabily, not as serious as that, but still very undesirable and returning 7221 for an input of 12345678901 is just wrong.

A test sketch follows

#include <SafeString.h>  // for simplicity see code below
// replace with actual String method code

// No memory fragmentation since any String objects used are release on return
// define this in the String class i.e.  bool String:toInt(const char* a, int& result) {
bool toInt(const char* a, int& result) {
  // result unchanged on error
  bool rtn = false;
  String s = a;
  // use SafeString method here for simplicity  see the SafeString library for the actual checks needed
  cSF(sfStr, 15);
  sfStr.readFrom(a);  // read at most 15 chars > max valid int length
  rtn = sfStr.toInt(result);
  return rtn;
}

void compareMethods(const char* inputInt) {
  int result = 0;
  String a = inputInt;
  Serial.print(" String.toInt(\"");
  Serial.print(inputInt);
  Serial.print("\") = ");
  result = a.toInt();
  Serial.println(result);

  Serial.print("    New toInt(\"");
  Serial.print(inputInt);
  Serial.print("\", result) = ");
  bool rtn = toInt(inputInt, result);
  Serial.println(rtn ? "true" : "false");
  if (rtn) {
    Serial.print("    result = ");
    Serial.println(result);
  }
  Serial.println();
}
void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  for (int i = 10; i > 0; i--) {
    Serial.print(i);
    Serial.print(" ");
    delay(500);
  }
  Serial.println();
  char invalidInt[] = "123a";
  compareMethods(invalidInt);
  char largeInt[] = "12345678901";
  compareMethods(largeInt);
  char veryLargeInt[] = "12345678901234567890";  // exceeds SaftString size
  compareMethods(veryLargeInt);
  char notInt[] = "abc";
  compareMethods(notInt);
  char notIntFloat[] = "1.55";
  compareMethods(notIntFloat);
  char validInt[] = "5522";
  compareMethods(validInt);
}

void loop() {
  // put your main code here, to run repeatedly:
}

It is how atoi works; not only in the Arduino world. You'll have to scrutinise the input before passing it to atoi.

The alternative is to use strtol.

Note: I got the following warning when I opened this topic

replaced link. I did not get that alert.

+1

C's atoi() and atol() have long been known to lack error handling, and strtol() is the documented replacement.

The C standard actually does not require atoi() to set errno, and if the result cannot be represented, the behavior is undefined. This is fully documented. Diligent and careful programmers know that.

When atoi() was first introduced in the early days of C in the 1970s, computer resources were extremely scarce. Because atoi() skips error checking, its fundamental implementation loop is incredibly compact and fits into just a few assembly instructions.

The function's lack of error checking is a direct byproduct of the era's design philosophies and structural constraints : in early C programming culture, standard library functions were intentionally lightweight. It was common practice to validate data before passing it to a processing function, rather than having the processing function perform the validation.

If a programmer needed to ensure a string was an integer, they would check the characters themselves or rely on upstream parsing systems (like a compiler front-end or strict data inputs) before passing the memory address to atoi(). The function was explicitly meant to do only one thing: convert digits into a mathematical number as fast as possible.

This reminds me the endless discussion about the need for the so called safestring library. The underlying question to @drmpf remark (which is fundamentally true and expected by design as explained) is why did Arduino do this ?

Arduino was aimed at artists, designers and beginners, and at simple educational projects where the input is expected to be correct. For that audience, one call like toInt() is the right level of simplicity. It returns zero when the conversion fails and has no error reporting, which was an acceptable trade for training wheels, not an oversight.

Modern C++ provides std::stoi, std::stol and std::stod, which are defined in terms of strtol and report errors by throwing. Diligent programmers check the result of every conversion, using strtol with an end pointer and errno check in C, and the std::sto family or std::from_chars in C++.

My view is that whoever outgrows the Arduino training wheels should move to the standard, and we (diligent programmers who understand the challenges and risks) should not try to hide that, change or bloat the String library's simplistic design, or add new confusing libraries.

We should ensure, as we did ourselves, that newcomers get the chance to learn and understand what is happening under the hood, why it is simple, where it breaks, and what the proper tools are. Hiding the risks or papering over them denies them the chance to learn, and that is how they grow into diligent programmers.

In an era where AI generates most of the code, we need more smart engineers who can double check and understand what is generated (or write it themselves), not a new generation of crippled programmers with no clue.

That's not a failure of ascii-to-whatever. It is a programmer failure to Vet The DATA they feed to functions.

Garbage In, Garbage Out is a LAW, not a missing gimmie.

This programmer began vetting data in 1981 and wrote his own User Input Tool Boxes over the years. It was part of being able to write code that did not crash after release in that age where "all code has bugs" was believed by customers and others. I was expected and 20 or so years later, expectation is no crashes.

Vet your data. Write your own routines that use the ascii-to-functions or not.. processing serial or buffered chars one at a time will teach A LOT and leave the coder able to make more as-it-happens code and see what happens when you use cycles that previously you let go by in wasteful ways.

The world is not here to fit anyone. There are just abusers forcing others to fit them. What kind of worker blames the tools?

This is an awful lot of unnecessary C++ String fiddling, just to get a supposedly safer alternative. atoi is a C function, and should not be implemented using C++!

Also: "memory fragmentation" isn't caused by not "releasing" dynamically allocated memory; it's caused by the memory functions being unable or unwilling to "consolidate" chunks of allocated memory when they are released. The claim that this is prevented by this code is ... unconvincing.

Am I missing something? Where is String s actually used in the function?