Why can't you just check if errno is equal to ERANGE?

The first code snippet is just plain wrong, and I'll explain why later, but first we need some background.

errno is a thread-local variable. It is set to a non-zero value when a system call or certain library functions fail. It remains unchanged when a system call succeeds. So it always contains the error number from last call that failed.

This means that you have two choices. Either set errno to 0 before each call, or use the standard idiom for errno. Here's the pseudo-code for the standard idiom

if ( foo() == some_value_that_indicates_that_an_error_occurred )
    then the value in errno applies to foo
else
    foo succeeded and the errno must be ignored because it could be anything

Most programmers will use the standard idiom, because setting errno to 0 before every system call is annoying and repetitive. Not to mention the fact that you might forget to set errno to 0 in the one place it actually matters.


Back to the first code snippet. It is wrong because there is no return value from strtol that unambiguously indicates that strtol failed. If strtol returns LONG_MAX, it could be that an error occurred, or the string actually contained the number LONG_MAX. There's no way to know whether the strtol call succeeded or failed. Which means that the standard idiom (which is what the first code snippet is trying to implement) cannot be used with strtol.

To use strtol correctly, you need to set errno to 0 before the call, like this

errno = 0;
result = strtol( buffer, &endptr, 10 );
if ( errno == ERANGE )
{
    // handle the error
    // ERANGE is the only error mentioned in the C specification
}
else if ( endptr == buffer )
{
    // handle the error
    // the conversion failed, i.e. the input string was empty,
    // or only contained whitespace, or the first non-whitespace 
    // character was not valid
}

Note that some implementations define other non-zero values for errno. See the applicable man page for details.


If you call

result = strtol("-2147483648", NULL, 0);

or

result = strtol("2147483647", NULL, 0);

on a 32-bit machine, you're going to get LONG_MIN or LONG_MAX in result, even though there hasn't been an error.

As user3386109 explained, one way to detect errors from strtol is to set errno to 0 first. The other way is to let it give you an end pointer and look at that. There are three or four cases:

char *endptr;
long int result = strtol(str, &endptr, 10);
if(*str == '\0') {
    /* str was empty */
} else if(endptr == str) {
    /* str was completely invalid */
} else if(*endptr != '\0') {
    /* numeric result followed by trailing nonnumeric character(s) */
} else {
    /* str was a completely valid number (perhaps with leading whitespace) */
}

Depending on your needs, the first two or three cases may be collapsed together. You may then need to worry (a) whether the "completely valid number" was representable (which you can test using errno), and (b) whether any "trailing nonnumeric character(s)" were innocuous whitespace (which, alas, strtol doesn't check for you, so if you care you'll have to check yourself).

Tags:

C

Strtol

Errno