19

according documentation:

On success, the function returns the converted integral number as a long int value. If no valid conversion could be performed, a zero value is returned. If the correct value is out of the range of representable values, LONG_MAX or LONG_MIN is returned, and the global variable errno is set to ERANGE.

Consider strtol(str, (char**)NULL, 10); if str is "0\0" how to know if the function failed or only has converted the string with "0" number?

phoxis
  • 60,131
  • 14
  • 81
  • 117
Jack
  • 16,276
  • 55
  • 159
  • 284
  • 2
    @StevenLuu: `scanf` has even worse error handling. Some implementations will generally give you garbage on overflow, but without signaling the error in any way whatsoever. – Dietrich Epp Jul 01 '12 at 05:37

5 Answers5

21

You need to pass a real pointer address if you want error checking, so you can distinguish 0 values arising from "0" and similar from 0 values arising from "pqr":

char *endptr;
errno = 0;
long result = strtol(str, &endptr, 10);
if (endptr == str)
{
    // nothing parsed from the string, handle errors or exit
}
if ((result == LONG_MAX || result == LONG_MIN) && errno == ERANGE)
{
    // out of range, handle or exit
}
// all went fine, go on
Daniel Fischer
  • 181,706
  • 17
  • 308
  • 431
  • Daniel: I need to ask you few things. Can you please come to [this chat](http://chat.stackoverflow.com/rooms/4064/haskell)? – Nawaz Jul 01 '12 at 05:57
  • 3
    why not just `if (errno == ERANGE)` to check for failure? – Undefined Mar 17 '15 at 01:37
  • 1
    @Undefined Good question. I think [vague memories] there was a reason why checking `ERANGE` alone was problematic, and the example code in [the man page](http://man7.org/linux/man-pages/man3/strtol.3.html) also does the value check together with the `ERANGE` check. But I have completely forgotten what that reason was. If there indeed was a reason. – Daniel Fischer Mar 17 '15 at 16:48
  • This is actually not correct way to check for failure.You should not check for errors by examining the return value of strtol, because the string might be a valid representation of 0l, LONG_MAX, or LONG_MIN. Instead, check whether tailptr points to what you expect after the number (e.g. '\0' if the string should end after the number). You also need to clear errno before the call and check it afterward, in case there was overflow – малин чекуров Jun 28 '18 at 12:02
  • @малинчекуров `errno` is cleared before the call, and `endptr` is checked to see whether anything was parsed or not. If something is parsed from the start of the string, we check whether we got an out-of-range value. So I don't see what you're aiming at. – Daniel Fischer Jun 28 '18 at 14:45
  • It just looks too complicated error checking, all you have to do is check whether the endptr points to what you expect – малин чекуров Jun 28 '18 at 15:09
  • 1
    But one doesn't always know what to expect after the number @малинчекуров, so how could one check whether it points to what one expects? – Daniel Fischer Jun 28 '18 at 15:12
  • Usually if you know where to look for the number then u usually know also what follows it. But let's agree on one thing - if you don't know what follows then use your check but if you do know what follows then you can use my way, much simpler and easier to read code. – малин чекуров Jun 28 '18 at 15:19
  • This deems that "pqr" is invalid, but "123pqr" is valid. Check that `endptr` points just past the end of `str` instead. -1 – vladr Aug 31 '18 at 19:08
  • @vladr That works if one wants to make sure that `str` contains nothing but a valid integer representation (plus possible initial whitespace). If one has other requirements, say that `str` contains other fields to be parsed after the `strtol` call, or one allows trailing whitespace, `endptr` will validly not point at the terminating `\0`. In such cases, one may want to check whether what `endptr` points to is legitimate. Or one can just be satisfied with having a valid integer at the start of `str` and not care at all what follows. – Daniel Fischer Aug 31 '18 at 20:09
5

IMHO, I prefer sscanf() to atoi() or strtol(). The main reason is that you cannot reliably check for error status on some platforms (i.e. Windows) unless you use sscanf() (which returns 1 if you succeed, and 0 if you fail).

nhahtdh
  • 55,989
  • 15
  • 126
  • 162
paulsm4
  • 114,292
  • 17
  • 138
  • 190
  • Why can't I check for error status on some platforms? IMHO: `strtol()` could returns a negative-value on failure.. – Jack Jul 01 '12 at 05:32
  • 1
    @Jack longs can be negative too. – Turix Jul 01 '12 at 05:32
  • 1
    `scanf` does NOT return failure on overflow. Conversion failure is easy with `strtol`, only overflow is annoying to check. – Dietrich Epp Jul 01 '12 at 05:39
  • 2
    Can you elaborate on why, e.g., Daniel Fischer's solution does not work on Windows? Contrary to the MSDN documentation it appears that the MSC runtime `errno` implementation **is** thread-safe, see http://stackoverflow.com/questions/6413052/msvc-errno-thread-safety – Dietrich Epp Jul 01 '12 at 05:45
5

Since the accepted answer is not actually a correct way to check for failure.

You should not check for errors by examining the return value of strtol, because the string might be a valid representation of 0l, LONG_MAX, or LONG_MIN. Instead, check whether tailptr points to what you expect after the number (e.g. '\0' if the string should end after the number). You also need to clear errno before the call and check it afterward, in case there was overflow.

2

I think I checked all the edge cases. If anyone thinks of an edge case that I missed please let me know in the comments and I will update this post. I tried to keep the error messages simple. If you disagree with this decision please feel free to change them as you see fit.

// Copyright (C) 2021 by cmwt
// Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

#include <errno.h>    // errno, ERANGE
#include <limits.h>   // LONG_MAX, LONG_MIN
#include <stdbool.h>  // true, false
#include <stddef.h>   // ptrdiff_t
#include <stdio.h>    // printf()
#include <stdlib.h>   // strtol()

struct ResultToLong {
  const char *err_msg;
  long        answer;
  ptrdiff_t   num_read;
  bool        is_func_success;
};

struct ResultToLong stringToLong(const char* start, int base) {
  struct ResultToLong result = {NULL, 0, 0, false};
  int save_errno = 0;
  char *end = NULL;

  if (base < 0 || base > 36) {
    result.err_msg = "Bad base: expect (0 <= base <= 36)";
    return result;
  }
  if (start == NULL) {
    result.err_msg = "Bad start: expect (start != NULL)";
    return result;
  }
  if (*start == '\0') {
    result.err_msg = "Bad start: start empty (const char* start == \"\";)";
    return result;
  }

  errno = 0;
  result.answer = strtol(start, &end, base);
  save_errno = errno;

  if (result.answer == 0 && *(start - 1) != '0') {
    result.err_msg = "Bad start: not a number";
    result.num_read = end - start;
    return result;
  }
  if (result.answer == LONG_MIN && save_errno == ERANGE) {
    result.err_msg = "Bad start: result < LONG_MIN";
    result.num_read = end - start;
    return result;
  }
  if (result.answer == LONG_MAX && save_errno == ERANGE) {
    result.err_msg = "Bad start: result > LONG_MAX";
    result.num_read = end - start;
    return result;
  }
  if (*end != '\0') {
  result.err_msg = "Warning: number in start is not '\\0' terminated";
  result.num_read = end - start;
  result.is_func_success = true;
  return result;
  }

  result.err_msg = "Success";
  result.num_read = end - start;
  result.is_func_success = true;
  return result;
}

int main() {
  struct ResultToLong result;
  const char* str;
  
  printf("Starting...\n\n");

  str= NULL;
  result = stringToLong(str, 0);
  printf("Response message: %s\n", result.err_msg);
  printf("Input string: %s\n", "<NULL>");
  printf("Number of chars processed: %ld\n", result.num_read);
  printf("Result: %ld\n\n", result.answer);
  
  str= "";
  result = stringToLong(str, 0);
  printf("Response message: %s\n", result.err_msg);
  printf("Input string: '%s'\n", str);
  printf("Number of chars processed: %ld\n", result.num_read);
  printf("Result: %ld\n\n", result.answer);

  str= "42";
  result = stringToLong(str, -1);
  printf("Response message: %s\n", result.err_msg);
  printf("Input string: '%s'\n", str);
  printf("Number of chars processed: %ld\n", result.num_read);
  printf("Result: %ld\n\n", result.answer);

  str= "42";
  result = stringToLong(str, 0);
  printf("Response message: %s\n", result.err_msg);
  printf("Input string: '%s'\n", str);
  printf("Number of chars processed: %ld\n", result.num_read);
  printf("Result: %ld\n\n", result.answer);

  str= "42 ";
  result = stringToLong(str, 0);
  printf("Response message: %s\n", result.err_msg);
  printf("Input string: '%s'\n", str);
  printf("Number of chars processed: %ld\n", result.num_read);
  printf("Result: %ld\n\n", result.answer);

  str= "                42";
  result = stringToLong(str, 0);
  printf("Response message: %s\n", result.err_msg);
  printf("Input string: '%s'\n", str);
  printf("Number of chars processed: %ld\n", result.num_read);
  printf("Result: %ld\n\n", result.answer);

  str= "0x42";
  result = stringToLong(str, 0);
  printf("Response message: %s\n", result.err_msg);
  printf("Input string: '%s'\n", str);
  printf("Number of chars processed: %ld\n", result.num_read);
  printf("Result: %ld\n\n", result.answer);

  str= "042";
  result = stringToLong(str, 0);
  printf("Response message: %s\n", result.err_msg);
  printf("Input string: '%s'\n", str);
  printf("Number of chars processed: %ld\n", result.num_read);
  printf("Result: %ld\n\n", result.answer);

  str= "+9999999999999999999";
  result = stringToLong(str, 0);
  printf("Response message: %s\n", result.err_msg);
  printf("Input string: '%s'\n", str);
  printf("Number of chars processed: %ld\n", result.num_read);
  printf("Result: %ld\n\n", result.answer);

  str= "-9999999999999999999";
  result = stringToLong(str, 0);
  printf("Response message: %s\n", result.err_msg);
  printf("Input string: '%s'\n", str);
  printf("Number of chars processed: %ld\n", result.num_read);
  printf("Result: %ld\n\n", result.answer);

  str= "?";
  result = stringToLong(str, 0);
  printf("Response message: %s\n", result.err_msg);
  printf("Input string: '%s'\n", str);
  printf("Number of chars processed: %ld\n", result.num_read);
  printf("Result: %ld\n\n", result.answer);

  printf("Done.\n");
}

If you object to returning a struct of this size by value then pass a pointer to the struct as an additional argument, just don't forget to handle the case where the struct pointer is NULL. My goal was to make this code easy to understand. You probbaly want combine checks and centralize setting the the return values.

Other Thoughts

I kinda wish strtol() would also return where the number starts. You could figure this out by iterating back from the end pointer but it might be slow.

cmwt
  • 351
  • 2
  • 15
1

You can either check the errno or pass a non-NULL value for the second argument and compare its resulting value to str, like:

char * endptr;
long result = strtol(str, &endptr, 10);
if (endptr > str)
{
    // Use result...
}
Turix
  • 4,470
  • 2
  • 19
  • 27