C Error Handling
Error handling means detecting when an operation failed and responding safely. C does not use exceptions, so programs usually report errors with return values, NULL pointers, and sometimes the global variable errno.
Good error handling keeps a program from using bad data, invalid pointers, missing files, or failed allocations as if everything worked.
Check Return Values
Many C functions signal failure through their return value. For example, malloc returns NULL when allocation fails, fopen returns NULL when a file cannot be opened, and scanf returns how many values it successfully read.
Your own functions can follow the same idea. A common pattern is to return 1 for success and 0 for failure, while storing the real result through a pointer argument.
#include <stdio.h>
int divide(int left, int right, int *result)
{
if (right == 0 || result == NULL) {
return 0;
}
*result = left / right;
return 1;
}
int main(void)
{
int answer;
if (divide(20, 4, &answer)) {
printf("20 / 4 = %d\n", answer);
} else {
printf("First division failed\n");
}
if (divide(20, 0, &answer)) {
printf("20 / 0 = %d\n", answer);
} else {
printf("Could not divide by zero\n");
}
return 0;
}
Output:
20 / 4 = 5
Could not divide by zero
The function checks both possible problems before writing to *result. If the divisor is zero, or if the caller passed a NULL pointer, the function returns failure and the caller decides what message to print.
Use errno For Library Errors
Some standard library functions store extra error information in errno, which is declared in <errno.h>. The value of errno is meaningful only after a function documents that it uses it and that function has reported failure.
Before calling such a function, set errno = 0. After the call, check both the function’s return result and errno according to that function’s rules.
Example: Validate A Number With strtol
The atoi function is simple, but it gives poor error information. For reliable parsing, use strtol. It can tell you where parsing stopped, and it can report a range error through errno.
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
int parse_int(const char *text, int *result)
{
char *end;
long value;
if (text == NULL || result == NULL) {
return 0;
}
errno = 0;
value = strtol(text, &end, 10);
if (end == text || *end != '\0') {
return 0;
}
if (errno == ERANGE || value < INT_MIN || value > INT_MAX) {
return -1;
}
*result = (int)value;
return 1;
}
int main(void)
{
const char *tests[] = {"42", "abc", "999999999999999999999999"};
int i;
for (i = 0; i < 3; i++) {
int number;
int status = parse_int(tests[i], &number);
if (status == 1) {
printf("%s -> %d\n", tests[i], number);
} else if (status == -1) {
printf("%s -> out of range\n", tests[i]);
} else {
printf("%s -> invalid number\n", tests[i]);
}
}
return 0;
}
Output:
42 -> 42
abc -> invalid number
999999999999999999999999 -> out of range
This function has three outcomes: 1 means success, 0 means the text was not a valid integer, and -1 means the text represented a number outside the range of int. The caller can handle each case separately.
Useful Error Tools
| Tool | Use |
|---|---|
return values |
Report success or failure from your own functions. |
NULL checks |
Detect failed pointer-returning functions such as fopen and malloc. |
errno |
Read extra error details from library functions that document its use. |
perror |
Print a short message followed by a system error message based on errno. |
strerror |
Convert an error number into a message string. |
Clean Up Before Returning
Error handling often means leaving a function early. Before returning, release anything you already acquired: close files with fclose, free allocated memory with free, and avoid using partially initialized data.
- Check errors immediately after the operation that can fail.
- Print messages that explain what failed, not only that something failed.
- Do not continue after an error if later code depends on the failed operation.
- Use one clear convention for your own function return values.
- Clean up resources before returning from an error path.
The key idea is that C error handling is explicit: check each risky operation, report failure clearly, and leave the program in a safe state.
