Why Error Checking Matters
Almost every system call and library function returns some type of status value indicating whether the call succeeded or failed. This return status should always be checked to determine whether the call was successful. If it was not, appropriate action must be taken — at the very least, the program should display an error message warning that something unexpected occurred.
Although it is tempting to skip these checks to save typing time, this is a false economy. Many hours of debugging time can be wasted because a check was not made on the return status of a system call or library function that “couldn’t possibly fail.”
Key Concepts
Handling System Call Errors
The manual page for each system call documents the possible return values of the call, showing which value indicates an error. Usually, an error is indicated by a return of -1. A system call can be checked with the following pattern:
fd = open(pathname, flags, mode); /* system call to open a file */
if (fd == -1) {
/* Code to handle the error */
}
/* ... rest of program ... */
When a system call fails, it sets the global integer variable errno to a positive value that identifies the specific error. Including the <errno.h> header file provides a declaration of errno and a set of constants for the various error numbers. All symbolic error names begin with the letter E (for example, EACCES, ENOENT, EINTR).
errno to 0. The variable may contain a nonzero value from a previous failed call. Always check the function return value first, and only examine errno if the return value indicates an error.#include <errno.h>
#include <stdio.h>
#include <unistd.h>
/* Example: diagnosing a read() system call error using errno */
ssize_t cnt;
cnt = read(fd, buf, numbytes);
if (cnt == -1) {
if (errno == EINTR)
fprintf(stderr, "read was interrupted by a signal\n");
else {
/* Some other error occurred */
fprintf(stderr, "read error: errno = %d\n", errno);
}
}
/* CORRECT order: always check return value FIRST, then errno */
fd = open(pathname, flags, mode);
if (fd == -1) {
/* Now it is safe to examine errno */
if (errno == ENOENT)
fprintf(stderr, "File not found: %s\n", pathname);
else if (errno == EACCES)
fprintf(stderr, "Permission denied: %s\n", pathname);
}
A few system calls (such as getpriority()) can legitimately return -1 on success. To determine whether an error occurs in such calls, you must:
- Set
errnoto0before making the system call. - After the call, check if it returned
-1anderrnois nonzero. - If both conditions are true, an error occurred.
#include <errno.h>
#include <sys/resource.h>
int prio;
errno = 0; /* Step 1: reset errno to 0 */
prio = getpriority(PRIO_PROCESS, 0);
if (prio == -1 && errno != 0) { /* Step 2: check both conditions */
/* An error occurred */
perror("getpriority");
}
/* If prio == -1 and errno == 0, the call succeeded with return value -1 */
errno to a nonzero value, though few functions do this. The safest approach is always to check the function return value before examining errno.Error-Reporting Functions
The perror() function prints the string pointed to by its msg argument, followed by a message corresponding to the current value of errno.
#include <stdio.h>
void perror(const char *msg);
/* Prototype: prints to stderr: "msg: error description\n"
The error description corresponds to the current errno value */
A typical pattern for handling errors from system calls using perror():
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int fd;
fd = open(pathname, flags, mode);
if (fd == -1) {
perror("open"); /* Prints: "open: No such file or directory" */
exit(EXIT_FAILURE);
}
/* General pattern for any system call */
if (some_syscall(args) == -1) {
perror("syscall name"); /* Prints descriptive error to stderr */
exit(EXIT_FAILURE);
}
perror() and strerror() are locale-sensitive. Error descriptions are displayed in the local language configured for the system.The strerror() function returns the error string corresponding to the error number given in its errnum argument. This gives you more control over how the error message is formatted and where it is displayed.
#include <string.h>
char *strerror(int errnum);
/* Returns pointer to error string corresponding to errnum
The string may be statically allocated — it could be overwritten
by subsequent calls to strerror() */
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
int fd;
fd = open(pathname, O_RDONLY);
if (fd == -1) {
/* More flexible than perror() — can format as needed */
fprintf(stderr, "Failed to open %s: %s\n", pathname, strerror(errno));
}
/* For an unrecognized error number, strerror() returns:
"Unknown error nnn" (Linux/glibc behavior)
NULL (some other implementations) */
strerror() may be statically allocated. This means it could be overwritten by subsequent calls to strerror(). In multithreaded programs, use strerror_r() instead, which writes into a caller-provided buffer.Error Handling for Library Functions
Library functions do not all report errors in the same way. They fall into three distinct categories:
Some library functions return error information in exactly the same way as system calls: a -1 return value with errno indicating the specific error. The remove() function is an example — it removes a file using unlink() or a directory using rmdir().
#include <stdio.h>
#include <errno.h>
/* remove() behaves like a system call for error reporting */
if (remove(pathname) == -1) {
perror("remove"); /* Works exactly like system call errors */
exit(EXIT_FAILURE);
}
Some library functions return a value other than -1 on error, but still set errno to indicate the specific error condition. For example, fopen() returns a NULL pointer on error, and the setting of errno depends on which underlying system call failed. The perror() and strerror() functions can still be used to diagnose these errors.
#include <stdio.h>
#include <errno.h>
FILE *fp;
fp = fopen(pathname, "r");
if (fp == NULL) { /* Returns NULL (not -1) on error */
perror("fopen"); /* But errno is still set — perror() works */
exit(EXIT_FAILURE);
}
Other library functions do not use errno at all. The method for determining the existence and cause of errors depends on the particular function and is documented in the function’s manual page. For these functions, it is a mistake to use errno, perror(), or strerror() to diagnose errors.
| Function Type | Error Indicator | errno Set? | How to Diagnose |
|---|---|---|---|
| Most system calls | Return -1 |
Yes | perror() or strerror(errno) |
| Library functions (Category 1) | Return -1 |
Yes | perror() or strerror(errno) |
| Library functions (Category 2) | Return NULL or other |
Yes | perror() or strerror(errno) |
| Library functions (Category 3) | Function-specific | No | Check the manual page |
| Calls returning -1 on success | Return -1 + errno != 0 |
Yes (on error) | Set errno=0 before; check both |
Always-succeed calls (getpid(), _exit()) |
N/A | No | No check needed |
- Always check the return status of every system call and library function — never skip error checks.
- Check the return value first, then examine
errnoonly when the return value indicates failure. - Do not assume
errnois 0 at the start of an operation — it may carry over from a previous call. - For calls that can return
-1on success, reseterrnoto 0 before calling and check both the return value anderrno. - Use
perror()for quick, simple error reporting tostderr. - Use
strerror()when you need more control over the format of the error message. - For library functions in Category 3, consult the manual page —
errno-based diagnostics will not work.
Next: Example Programs and Error-Diagnostic Functions
Explore the common header files and error-handling functions used in real system programming projects.
