Skip to content

C Error Handling

C programming does not have built-in exception handling like some other languages (e.g., C++, Java). Instead, error handling primarily relies on checking return values from functions and sometimes inspecting a global error number variable, errno.

Many standard library functions, especially those dealing with system resources (like file I/O) or memory allocation, return a special value (often NULL, -1, or another value documented for the specific function) to indicate an error. When an error occurs, these functions often set the global integer variable errno (defined in <errno.h>) to a specific error code providing more details.

Important Rule: Always check the function’s return value first to see if an error occurred. Only if the return value indicates an error should you check errno to understand why it failed. Some functions might successfully return a value that could be mistaken for an error indicator (like -1), so the return value check is paramount.

It’s good practice to include <errno.h> when using errno. Note that errno is thread-local in modern multi-threaded environments, meaning each thread gets its own errno.

C provides two standard functions to get human-readable error messages corresponding to errno values:

  • The perror(const char *s) function (from <stdio.h>) prints a custom message provided by you (s), followed by a colon, a space, and the textual representation of the current errno value. The output is sent to the standard error stream (stderr).
  • The strerror(int errnum) function (from <string.h>) returns a pointer to a string containing the textual representation of the error code passed in errnum. You typically pass the current value of errno to it.

Example: Simulating a file opening error:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h> // Required for EXIT_FAILURE
// Note: No need to declare 'extern int errno;' explicitly when including <errno.h>
int main(void) { // Use standard main signature
FILE *pf;
int errnum;
// Attempt to open a non-existent file for reading
pf = fopen("nonexistent_file.txt", "rb");
// ALWAYS check the return value first!
if (pf == NULL) {
errnum = errno; // Store errno immediately, as other calls might change it
fprintf(stderr, "Error opening file.\n"); // Informative message
// Method 1: Using errno directly (less user-friendly)
fprintf(stderr, "Value of errno: %d\n", errnum);
// Method 2: Using perror (recommended for simple messages)
perror("Error reported by perror");
// Method 3: Using strerror (useful for custom formatting)
fprintf(stderr, "Error opening file: %s\n", strerror(errnum));
return EXIT_FAILURE; // Indicate failure using standard macro
} else {
printf("File opened successfully! Closing it.\n");
fclose(pf);
}
return EXIT_SUCCESS; // Indicate success using standard macro
}

When compiled and executed, assuming nonexistent_file.txt does not exist, the output would resemble (the exact errno value might differ slightly based on OS):

Error opening file.
Value of errno: 2
Error reported by perror: No such file or directory
Error opening file: No such file or directory

Integer division by zero results in undefined behavior in C. It might crash the program or produce unexpected results. Floating-point division by zero is defined by the IEEE 754 standard (resulting in infinity or NaN) but is often still considered an error condition.

You must explicitly check for a zero divisor before performing the division:

#include <stdio.h>
#include <stdlib.h> // For exit() and EXIT_FAILURE
int main(void) {
int dividend = 20;
int divisor = 0;
int quotient;
// Check before dividing
if (divisor == 0) {
fprintf(stderr, "Error: Division by zero is not allowed.\n");
return EXIT_FAILURE; // Exit indicating failure
}
// This part will only execute if divisor is non-zero
quotient = dividend / divisor;
printf("Value of quotient: %d\n", quotient); // Use printf for normal output
return EXIT_SUCCESS; // Exit indicating success
}

When compiled and executed, this produces:

Error: Division by zero is not allowed.

The <assert.h> header provides the assert macro. It’s used to check conditions that should always be true during development. If the condition inside assert() is false, the program prints an error message (including the file name, line number, and the condition) to stderr and terminates.

assert is primarily a debugging tool. Assertions can be disabled in release builds by defining the NDEBUG macro before including <assert.h> (often done via compiler flags like -DNDEBUG), removing any performance overhead.

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
// Disable assertions by uncommenting the next line or using -DNDEBUG
// #define NDEBUG
int main(void) {
int divisor = 0;
// This assertion will fail
assert(divisor != 0);
// This line will not be reached if assertions are enabled
printf("Continuing execution (only if NDEBUG is defined).\n");
return EXIT_SUCCESS;
}

With assertions enabled (default), running this might produce output like:

assertion_example: main.c:12: main: Assertion `divisor != 0' failed.

When a program finishes, it returns an exit status to the operating system. This status indicates whether the program completed successfully or encountered an error. The standard macros for this are defined in <stdlib.h>:

  • EXIT_SUCCESS: Typically defined as 0. Indicates successful execution.
  • EXIT_FAILURE: Typically defined as a non-zero value (often 1). Indicates that the program terminated due to an error.

Using these macros makes your program more portable and readable than using magic numbers like 0 or -1 directly in return statements from main or in exit() calls.

Example using standard exit codes:

#include <stdio.h>
#include <stdlib.h> // Required for EXIT_SUCCESS, EXIT_FAILURE
int main(void) {
int dividend = 20;
int divisor = 5;
int quotient;
if (divisor == 0) {
fprintf(stderr, "Error: Division by zero!\n");
return EXIT_FAILURE; // Use standard macro for failure
}
quotient = dividend / divisor;
printf("Value of quotient: %d\n", quotient);
return EXIT_SUCCESS; // Use standard macro for success
}

When compiled and executed (with divisor = 5), it produces:

Value of quotient: 4