C Error Handling
Unlike languages like Python or Java, C has no built-in exception handling system. There is no try-catch in C. Instead, C programs detect and handle errors through return values, the errno variable, and standard error-reporting functions. Learning to handle errors correctly makes your C programs reliable, predictable, and professional.
Why Error Handling Matters
Imagine a program that opens a file, reads data, and processes it. If the file does not exist and the program does not check for this, it will crash or produce wrong results silently. Proper error handling catches these situations and responds appropriately — showing a message, retrying, or exiting cleanly.
Method 1: Return Values
The most common error-handling method in C is checking the return value of functions. Most C standard library functions return a special value (like -1, NULL, or 0) when something goes wrong.
Example: Checking File Open Failure
#include <stdio.h>
int main()
{
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) // NULL means fopen failed
{
printf("Error: Cannot open file.\n");
return 1; // return non-zero to indicate failure
}
printf("File opened successfully.\n");
fclose(fp);
return 0; // return 0 means success
}
Return Value Conventions
| Function Category | Success Return | Error Return |
|---|---|---|
| File functions (fopen) | Valid pointer | NULL |
| Memory functions (malloc) | Valid pointer | NULL |
| System calls (read, write) | Number of bytes | -1 |
| Math functions | Result value | Specific error value |
| main() / exit code | 0 | Non-zero |
Method 2: errno — The Error Number Variable
When a library function fails, it sets a global variable called errno to a specific integer code that tells you what went wrong. Include <errno.h> to use it.
#include <stdio.h>
#include <errno.h>
int main()
{
FILE *fp = fopen("nonexistent.txt", "r");
if (fp == NULL)
{
printf("errno value: %d\n", errno); // prints the error code
return 1;
}
fclose(fp);
return 0;
}
Common errno Values
| errno Code | Macro Name | Meaning |
|---|---|---|
| 1 | EPERM | Operation not permitted |
| 2 | ENOENT | No such file or directory |
| 12 | ENOMEM | Out of memory |
| 13 | EACCES | Permission denied |
| 22 | EINVAL | Invalid argument |
| 28 | ENOSPC | No space left on device |
Method 3: perror() — Print Error Message
perror() prints a human-readable error message for the current value of errno. It automatically appends the system's description of the error.
#include <stdio.h>
int main()
{
FILE *fp = fopen("missing.txt", "r");
if (fp == NULL)
{
perror("fopen failed"); // prints: fopen failed: No such file or directory
return 1;
}
fclose(fp);
return 0;
}
Output:
fopen failed: No such file or directory
Method 4: strerror() — Get Error String
strerror(errno) returns the error description as a string. Use it when you want to include the error message in a custom log or format.
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{
FILE *fp = fopen("missing.txt", "r");
if (fp == NULL)
{
printf("Error %d: %s\n", errno, strerror(errno));
// Output: Error 2: No such file or directory
return 1;
}
fclose(fp);
return 0;
}
Method 5: assert() — Catch Logic Bugs
assert(condition) checks a condition at runtime. If the condition is false, the program prints an error message and terminates. It is used to catch programming mistakes during development.
#include <stdio.h>
#include <assert.h>
int divide(int a, int b)
{
assert(b != 0); // crash with message if b is 0
return a / b;
}
int main()
{
printf("%d\n", divide(10, 2)); // Output: 5
printf("%d\n", divide(10, 0)); // Assertion failed: b != 0
return 0;
}
Assertions are disabled when you compile with -DNDEBUG, so they add zero cost in production builds.
Method 6: exit() with Status Codes
exit() terminates the program and returns a status code to the operating system.
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *fp = fopen("config.txt", "r");
if (fp == NULL)
{
fprintf(stderr, "Critical error: config.txt not found.\n");
exit(EXIT_FAILURE); // exit code 1 — indicates failure
}
// ... process file ...
fclose(fp);
exit(EXIT_SUCCESS); // exit code 0 — indicates success
}
| Constant | Value | Meaning |
|---|---|---|
EXIT_SUCCESS | 0 | Program ended successfully |
EXIT_FAILURE | 1 | Program ended due to an error |
Writing to stderr
Error messages should go to stderr (standard error), not stdout (standard output). This allows users and scripts to separate normal output from error messages.
#include <stdio.h>
int main()
{
int age = -5;
if (age < 0)
{
fprintf(stderr, "Error: Age cannot be negative (%d)\n", age);
return 1;
}
printf("Age: %d\n", age);
return 0;
}
Error Handling Pattern: Check Every Function Call
#include <stdio.h>
#include <stdlib.h>
int main()
{
// Step 1: Open file — check for failure
FILE *fp = fopen("data.txt", "r");
if (!fp) { perror("Cannot open data.txt"); return 1; }
// Step 2: Allocate memory — check for failure
int *buffer = (int*) malloc(100 * sizeof(int));
if (!buffer) {
fprintf(stderr, "Memory allocation failed\n");
fclose(fp); // clean up already-opened file
return 1;
}
// Step 3: Use file and buffer safely
printf("File opened and memory allocated.\n");
// Step 4: Clean up (always happens)
free(buffer);
fclose(fp);
return 0;
}
Error Handling Flow Diagram
Function call
|
v
Success? ──YES──> Continue program normally
|
NO
|
v
Set errno (automatic for system functions)
|
v
Check return value (NULL / -1 / 0)
|
v
Decide response:
- perror() / strerror() → print message
- return error code → let caller handle
- exit(EXIT_FAILURE) → terminate cleanly
- assert() → crash during development
Summary
C handles errors through return values, the errno variable, and standard error-reporting functions. Every function call that can fail must be checked — especially file operations, memory allocation, and system calls. The errno variable is automatically set by library functions when an error occurs. perror() prints a human-readable error description using the current errno value, while strerror(errno) returns the error string for custom formatting. assert() catches programming logic bugs during development. Error messages belong on stderr, not stdout. Always clean up resources like files and heap memory before exiting, even when an error occurs. Disciplined error handling is the difference between a program that crashes silently and one that fails safely and informatively.
