Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct usage of strtol

Tags:

c++

c

The program below converts a string to long, but based on my understanding it also returns an error. I am relying on the fact that if strtol successfully converted string to long, then the second parameter to strtol should be equal to NULL. When I run the below application with 55, I get the following message.

./convertToLong 55 Could not convert 55 to long and leftover string is: 55 as long is 55 

How can I successfully detect errors from strtol? In my application, zero is a valid value.

Code:

#include <stdio.h> #include <stdlib.h>  static long parseLong(const char * str);  int main(int argc, char ** argv) {     printf("%s as long is %ld\n", argv[1], parseLong(argv[1]));     return 0;  }  static long parseLong(const char * str) {     long _val = 0;     char * temp;      _val = strtol(str, &temp, 0);      if(temp != '\0')             printf("Could not convert %s to long and leftover string is: %s", str, temp);      return _val; } 
like image 950
Jimm Avatar asked Jan 05 '13 20:01

Jimm


People also ask

What does strtol do in C?

The strtol library function in C converts a string to a long integer. The function works by ignoring any whitespace at the beginning of the string, converting the next characters into a long integer, and stopping when it comes across the first non-integer character.

Does strtol change the string?

The strtoll() function converts a character string to a long long integer value. The parameter nptr points to a sequence of characters that can be interpreted as a numeric value of type long long int.

How do I know if strtol failed?

Since strtol() can legitimately return 0, LONG_MAX, or LONG_MIN (LLONG_MAX or LLONG_MIN for strtoll()) on both success and failure, the calling program should set errno to 0 before the call, and then determine if an error occurred by checking whether errno has a nonzero value after the call. According to POSIX.

What is Endptr in C?

endptr − This is the reference to an object of type char*, whose value is set by the function to the next character in str after the numerical value. base − This is the base, which must be between 2 and 36 inclusive, or be the special value 0.


1 Answers

Note that names beginning with an underscore are reserved for the implementation; it is best to avoid using such names in your code. Hence, _val should be just val.

The full specification of error handling for strtol() and its relatives is complex, surprisingly complex, when you first run across it. One thing you're doing absolutely right is using a function to invoke strtol(); using it 'raw' in code is probably not correct.

Since the question is tagged with both C and C++, I will quote from the C2011 standard; you can find the appropriate wording in the C++ standard for yourself.

ISO/IEC 9899:2011 §7.22.1.4 The strtol, strtoll, strtoul and strtoull functions

long int strtol(const char * restrict nptr, char ** restrict endptr, int base);

¶2 [...] First, they decompose the input string into three parts: an initial, possibly empty, sequence of white-space characters (as specified by the isspace function), a subject sequence resembling an integer represented in some radix determined by the value of base, and a final string of one or more unrecognized characters, including the terminating null character of the input string. [...]

¶7 If the subject sequence is empty or does not have the expected form, no conversion is performed; the value of nptr is stored in the object pointed to by endptr, provided that endptr is not a null pointer.

Returns

¶8 The strtol, strtoll, strtoul, and strtoull functions return the converted value, if any. If no conversion could be performed, zero is returned. If the correct value is outside the range of representable values, LONG_MIN, LONG_MAX, LLONG_MIN, LLONG_MAX, ULONG_MAX, or ULLONG_MAX is returned (according to the return type and sign of the value, if any), and the value of the macro ERANGE is stored in errno.

Remember that no standard C library function ever sets errno to 0. Therefore, to be reliable, you must set errno to zero before calling strtol().

So, your parseLong() function might look like:

static long parseLong(const char *str) {     errno = 0;     char *temp;     long val = strtol(str, &temp, 0);      if (temp == str || *temp != '\0' ||         ((val == LONG_MIN || val == LONG_MAX) && errno == ERANGE))         fprintf(stderr, "Could not convert '%s' to long and leftover string is: '%s'\n",                 str, temp);         // cerr << "Could not convert '" << str << "' to long and leftover string is '"         //      << temp << "'\n";     return val; } 

Note that on error, this returns 0 or LONG_MIN or LONG_MAX, depending on what strtol() returned. If your calling code needs to know whether the conversion was successful or not, you need a different function interface — see below. Also, note that errors should be printed to stderr rather than stdout, and error messages should be terminated by a newline \n; if they're not, they aren't guaranteed to appear in a timely fashion.

Now, in library code you probably do not want any printing, and your calling code might want to know whether the conversion was successful of not, so you might revise the interface too. In that case, you'd probably modify the function so it returns a success/failure indication:

bool parseLong(const char *str, long *val) {     char *temp;     bool rc = true;     errno = 0;     *val = strtol(str, &temp, 0);      if (temp == str || *temp != '\0' ||         ((*val == LONG_MIN || *val == LONG_MAX) && errno == ERANGE))         rc = false;      return rc; } 

which you could use like:

if (parseLong(str, &value))     …conversion successful… else     …handle error… 

If you need to distinguish between 'trailing junk', 'invalid numeric string', 'value too big' and 'value too small' (and 'no error'), you'd use an integer or enum instead of a boolean return code. If you want to allow trailing white space but no other characters, or if you don't want to allow any leading white space, you have more work to do in the function. The code allows octal, decimal and hexadecimal; if you want strictly decimal, you need to change the 0 to 10 in the call to strtol().

If your functions are to masquerade as part of the standard library, they should not set errno to 0 permanently, so you'd need to wrap the code to preserve errno:

int saved = errno;  // At the start, before errno = 0;  …rest of function…  if (errno == 0)     // Before the return     errno = saved; 
like image 103
Jonathan Leffler Avatar answered Oct 05 '22 17:10

Jonathan Leffler