Correct usage of strtol
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
andstrtoull
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 byendptr
, provided thatendptr
is not a null pointer.Returns
¶8 The
strtol
,strtoll
,strtoul
, andstrtoull
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 inerrno
.
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;
You're almost there. temp
itself will not be null, but it will point to a null character if the whole string is converted, so you need to dereference it:
if (*temp != '\0')
How can I successfully detect errors from strtol?
static long parseLong(const char * str) {
int base = 0;
char *endptr;
errno = 0;
long val = strtol(str, &endptr, base);
3 tests specified/supported by the standard C library:
-
Any conversion done?
if (str == endptr) puts("No conversion.");
-
In range?
else if (errno == ERANGE) puts("Input out of long range.");
-
Tailing junk?
else if (*endptr) puts("Extra junk after the numeric text.");
Success
else printf("Success %ld\n", val);
Input like str == NULL
or base
not 0, [2 to 36] is undefined behavior. Various implementations (extensions to the C library) provide defined behavior and report via errno
. We could add a 4th test.
else if (errno) puts("Some implementation error found.");
Or combine with the errno == ERANGE
test.
Sample terse code that also takes advantage of common implementation extensions.
long my_parseLong(const char *str, int base, bool *success) {
char *endptr = 0;
errno = 0;
long val = strtol(str, &endptr, base);
if (success) {
*success = endptr != str && errno == 0 && endptr && *endptr == '\0';
}
return val;
}
You're missing a level of indirection. You want to check whether the character is the terminating NUL
, and not if the pointer is NULL
:
if (*temp != '\0')
By the way, this is not a good approach for error checking. The proper error checking method of the strto*
family of functions is not done by comparing the output pointer with the end of the string. It should be done by checking for a zero return value and getting the return value of errno
.
You should be checking
*temp != '\0'
You should also be able to check the value of errno after calling strotol according to this:
RETURN VALUES
The strtol(), strtoll(), strtoimax(), and strtoq() functions return the result
of the conversion, unless the value would underflow or overflow. If no conver-
sion could be performed, 0 is returned and the global variable errno is set to
EINVAL (the last feature is not portable across all platforms). If an overflow
or underflow occurs, errno is set to ERANGE and the function return value is
clamped according to the following table.
Function underflow overflow
strtol() LONG_MIN LONG_MAX
strtoll() LLONG_MIN LLONG_MAX
strtoimax() INTMAX_MIN INTMAX_MAX
strtoq() LLONG_MIN LLONG_MAX