Rationale behind C library functions never setting errno to zero

cstandards

The C standard mandates that no C standard library functions shall set errno to zero. Why exactly is this?

I could understand it being useful for calling several functions, and only checking errno after the last one – for example:

errno = 0;
double x = strtod(str1, NULL);
long y = strtol(str2, NULL);
if (errno)
    // either "strtod" or "strtol" failed
else
    // both succeeded

However, is this not considered "bad practice"? Since you're only checking errno at the very end, you only know that one of the functions did fail, but not which function failed. Is simply knowing that something failed good enough for most practical programs?

I tried looking for various C Rationale documents, but many of them don't have much detail for <errno.h>.

Best Answer

The C library does not set errno to 0 for historical reasons1. POSIX no longer claims its libraries will not alter the value in case of success, and the new Linux man page for errno.h reflects this:

The <errno.h> header file defines the integer variable errno, which is set by system calls and some library functions in the event of an error to indicate what went wrong. Its value is significant only when the return value of the call indicated an error (i.e., -1 from most system calls; -1 or NULL from most library functions); a function that succeeds is allowed to change errno.

The ANSI C Rationale states that the committee felt it was more practical to adopt and standardize the existing practice of using errno.

The error reporting machinery centered about the setting of errno is generally regarded with tolerance at best. It requires a ``pathological coupling'' between library functions and makes use of a static writable memory cell, which interferes with the construction of shareable libraries. Nevertheless, the Committee preferred to standardize this existing, however deficient, machinery rather than invent something more ambitious.

There is almost always a way to check for error outside of checking if errno got set. Checking if errno got set is not always reliable, since some calls require calling a separate API to get the error reason. For instance, ferror() is used to check for an error if you get a short result from fread() or fwrite().

Interestingly enough, your example of using strtod() is one of the cases where setting errno to 0 before the call is required to correctly detect if an error has occurred. All the strto*() string to number functions have this requirement, because a valid return value is returned even in the face of an error.

errno = 0;
char *endptr;
double x = strtod(str1, &endptr);
if (endptr == str1) {
    /*...parse error */
} else if (errno == ERANGE) {
    if (x == 0) {
        /*...underflow */
    } else if (x == HUGE_VAL) {
        /*...positive overflow */
    } else if (x == -HUGE_VAL) {
        /*...negative overflow */
    } else {
        /*...unknown range error? */
    }
}

The above code is based on the behavior of strtod() as documented on Linux. The C standard only stipulates that underflow cannot return a value greater than the smallest positive double, and whether or not errno is set to ERANGE is implementation defined2.

There is actually an extensive cert advisory write-up that recommends always setting errno to 0 before a library call and checking its value after the call indicates a failure has occurred. This is because some library calls will set errno even if the call itself was successful3.

The value of errno is 0 at program startup, but it is never set to 0 by any library function. The value of errno may be set to nonzero by a library function call whether or not there is an error, provided the use of errno is not documented in the description of the function in the C Standard. It is meaningful for a program to inspect the contents of errno only after an error has been reported. More precisely, errno is meaningful only after a library function that sets errno on error has returned an error code.


1. Previously I claimed it was to avoid masking an error from an earlier call. I can't find any evidence to support this claim. I also had a bogus printf() example.
2. Thanks to @chux for pointing this out. Reference is C.11 §7.22.1.3 ¶10.
3. Pointed out by @KeithThompson in a comment.

Related Topic