Error Handling in C: Complete Guide with Examples
Error Handling in C: A Comprehensive Tutorial
Error handling in C is performed using function return values, error codes, and the global errno variable, since C does not provide built-in exception handling like try-catch. Most functions return -1, NULL, or set errno when an error occurs. Understanding error handling is essential for writing robust, reliable programs that gracefully handle failures.
This tutorial provides an in-depth exploration of error handling in C: errno, perror, strerror, ferror, feof, clearerr, exit statuses, manual validation, common pitfalls, and best practices.
Conceptual Introduction & Fundamentals
What is Error Handling?
Error handling is the process of detecting, reporting, and responding to errors that occur during program execution. In C, this is done through:
- Return values: Functions return special values (
NULL,-1,EOF) on error. - Error codes: The
errnovariable stores specific error codes. - Error reporting:
perrorandstrerrorprovide descriptive messages. - Exit statuses: Programs communicate success/failure to the OS.
Why Error Handling Matters
- Prevent crashes: Detect errors before they cause undefined behavior.
- Inform users: Provide clear error messages.
- Resource management: Clean up allocated resources on failure.
- Debugging: Identify the root cause of failures.
- Robustness: Handle unexpected conditions gracefully.
C vs. Exception-Based Languages
C does not have built-in exception handling (try-catch). Instead, errors are handled through:
- Checking return values
- Checking
errno - Manual validation of conditions
- Early returns or
exit()calls
Syntax & Core Mechanics
errno — Error Number
errno is a global variable defined in <errno.h> that stores the error code when a library function fails.
c
#include <stdio.h> #include <errno.h> int main(void) { FILE *fp = fopen("missing.txt", "r"); if (fp == NULL) { printf("Error Code: %d\n", errno); perror("Error"); } return 0; } Expected output:
text
Error Code: 2 Error: No such file or directory
Common errno values:
| errno | Meaning |
|---|---|
| 1 | Operation not permitted |
| 2 | No such file or directory |
| 3 | No such process |
| 5 | I/O error |
| 12 | Out of memory |
| 13 | Permission denied |
perror() — Print Error Message
perror prints a descriptive error message to stderr based on the current errno.
c
#include <stdio.h> #include <errno.h> int main(void) { FILE *fp = fopen("nonexistent.txt", "r"); if (fp == NULL) { perror("Error opening file"); } return 0; } Expected output:
text
Error opening file: No such file or directory
strerror() — Error String
strerror returns a pointer to the textual description of an error code.
c
#include <stdio.h> #include <string.h> #include <errno.h> int main(void) { FILE *fp = fopen("nonexistent.txt", "r"); if (fp == NULL) { printf("Error: %s\n", strerror(errno)); } return 0; } Expected output:
text
Error: No such file or directory
ferror() — File Error Check
ferror checks if an error occurred during a file operation.
c
#include <stdio.h> int main(void) { FILE *fp = fopen("test.txt", "w"); if (fp == NULL) { perror("Error opening file"); return 1; } fprintf(fp, "Hello, World!\n"); if (ferror(fp) == 0) { printf("Data written successfully.\n"); } else { printf("Error writing data.\n"); } fclose(fp); return 0; } Expected output:
text
Data written successfully.
feof() — End of File Check
feof checks whether the end of a file has been reached.
c
#include <stdio.h> int main(void) { FILE *fp = fopen("test.txt", "r"); if (fp == NULL) { perror("Error opening file"); return 1; } int ch; while ((ch = fgetc(fp)) != EOF) { putchar(ch); } if (feof(fp)) { printf("\nEnd of file reached.\n"); } fclose(fp); return 0; } clearerr() — Clear Error Flags
clearerr clears the error and EOF flags for a stream.
c
#include <stdio.h> int main(void) { FILE *fp = fopen("test.txt", "w+"); if (fp == NULL) { perror("Error opening file"); return 1; } fprintf(fp, "Hello"); /* Read to trigger EOF */ while (fgetc(fp) != EOF); if (feof(fp)) { printf("EOF encountered.\n"); } /* Clear EOF flag */ clearerr(fp); if (!feof(fp)) { printf("EOF flag cleared.\n"); } fclose(fp); return 0; } Expected output:
text
EOF encountered. EOF flag cleared.
Exit Statuses
Use EXIT_SUCCESS and EXIT_FAILURE from <stdlib.h>.
c
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> int main(void) { FILE *fp = fopen("nonexistent.txt", "rb"); if (fp == NULL) { fprintf(stderr, "Error: %s\n", strerror(errno)); exit(EXIT_FAILURE); } fclose(fp); exit(EXIT_SUCCESS); } Manual Error Handling
Validate conditions before executing operations.
c
#include <stdio.h> int main(void) { int num1 = 10, num2 = 0; if (num2 == 0) { fprintf(stderr, "Error: Division by zero is not allowed\n"); return 1; } printf("Result: %d\n", num1 / num2); return 0; } Expected output:
text
Error: Division by zero is not allowed
Full Practical Code Example with Explanation
This program demonstrates comprehensive error handling in a realistic scenario: a file processing utility with multiple error checks.
Program: Robust File Processor
c
/** * error_handling_demo.c * Demonstrates comprehensive error handling in C. * Includes errno, perror, strerror, ferror, feof, clearerr, and exit statuses. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define MAX_LINE_LEN 256 #define MAX_FILENAME_LEN 100 /* Function prototypes */ int copyFile(const char *source, const char *destination); int readFileWithValidation(const char *filename); int divideNumbers(int a, int b, double *result); void demonstrateErrno(void); void demonstrateFileErrors(void); void demonstrateManualValidation(void); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Error Handling Demo ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Demonstrate errno, perror, strerror\n"); printf("2. Demonstrate File Errors (ferror, feof, clearerr)\n"); printf("3. Demonstrate Manual Validation\n"); printf("4. Copy File with Error Handling\n"); printf("5. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demonstrateErrno(); break; case 2: demonstrateFileErrors(); break; case 3: demonstrateManualValidation(); break; case 4: { char source[MAX_FILENAME_LEN]; char destination[MAX_FILENAME_LEN]; printf("Enter source file: "); fflush(stdout); if (fgets(source, sizeof(source), stdin) == NULL) break; source[strcspn(source, "\n")] = '\0'; printf("Enter destination file: "); fflush(stdout); if (fgets(destination, sizeof(destination), stdin) == NULL) break; destination[strcspn(destination, "\n")] = '\0'; if (copyFile(source, destination) == 0) { printf("File copied successfully.\n"); } break; } case 5: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Demonstrates errno, perror, and strerror */ void demonstrateErrno(void) { printf("\n=== errno, perror, strerror ===\n"); /* Attempt to open a non-existent file */ FILE *fp = fopen("nonexistent_file.txt", "r"); if (fp == NULL) { printf("errno value: %d\n", errno); printf("strerror: %s\n", strerror(errno)); perror("perror message"); } /* Attempt to allocate too much memory */ printf("\nAttempting large allocation:\n"); int *ptr = malloc((size_t)-1); if (ptr == NULL) { printf("errno value: %d\n", errno); printf("strerror: %s\n", strerror(errno)); perror("malloc error"); } else { free(ptr); } } /* Demonstrates file error functions */ void demonstrateFileErrors(void) { printf("\n=== File Errors (ferror, feof, clearerr) ===\n"); /* Create a test file */ FILE *fp = fopen("test_errors.txt", "w"); if (fp == NULL) { perror("Error creating file"); return; } fprintf(fp, "Hello, Error Handling!\n"); fclose(fp); /* Read file with error checking */ fp = fopen("test_errors.txt", "r"); if (fp == NULL) { perror("Error opening file"); return; } int ch; while ((ch = fgetc(fp)) != EOF) { putchar(ch); } if (feof(fp)) { printf("End of file reached.\n"); } if (ferror(fp)) { printf("Read error occurred.\n"); } /* Clear EOF flag */ clearerr(fp); if (!feof(fp)) { printf("EOF flag cleared.\n"); } fclose(fp); /* Clean up */ remove("test_errors.txt"); } /* Demonstrates manual validation */ void demonstrateManualValidation(void) { printf("\n=== Manual Validation ===\n"); int a = 10, b = 0; double result; if (divideNumbers(a, b, &result) == 0) { printf("%d / %d = %.2f\n", a, b, result); } /* Array bounds checking */ int arr[5] = {1, 2, 3, 4, 5}; int index = 10; if (index >= 0 && index < 5) { printf("arr[%d] = %d\n", index, arr[index]); } else { fprintf(stderr, "Error: Index %d out of bounds.\n", index); } /* Input validation */ int age = -5; if (age < 0 || age > 150) { fprintf(stderr, "Error: Invalid age %d.\n", age); } } /* Safely divides two numbers */ int divideNumbers(int a, int b, double *result) { if (b == 0) { fprintf(stderr, "Error: Division by zero.\n"); return -1; } *result = (double)a / b; return 0; } /* Copies a file with comprehensive error handling */ int copyFile(const char *source, const char *destination) { FILE *src = fopen(source, "rb"); if (src == NULL) { fprintf(stderr, "Error opening source '%s': %s\n", source, strerror(errno)); return -1; } FILE *dst = fopen(destination, "wb"); if (dst == NULL) { fprintf(stderr, "Error opening destination '%s': %s\n", destination, strerror(errno)); fclose(src); return -1; } char buffer[4096]; size_t bytesRead; while ((bytesRead = fread(buffer, 1, sizeof(buffer), src)) > 0) { size_t bytesWritten = fwrite(buffer, 1, bytesRead, dst); if (bytesWritten != bytesRead) { fprintf(stderr, "Error writing to destination: %s\n", strerror(errno)); fclose(src); fclose(dst); return -1; } } if (ferror(src)) { fprintf(stderr, "Error reading from source: %s\n", strerror(errno)); fclose(src); fclose(dst); return -1; } fclose(src); fclose(dst); return 0; } Explanation of Error Handling Usage
errno: Stores error codes from failed functions.perror: Prints error messages to stderr.strerror: Returns error description string.ferror: Checks file operation errors.feof: Checks end-of-file condition.clearerr: Clears error and EOF flags.- Manual validation: Checks conditions before operations.
- Exit statuses: Uses
EXIT_SUCCESSandEXIT_FAILURE.
Expected Output (sample run)
text
=== Error Handling Demo === --- Menu --- 1. Demonstrate errno, perror, strerror 2. Demonstrate File Errors (ferror, feof, clearerr) 3. Demonstrate Manual Validation 4. Copy File with Error Handling 5. Exit Enter choice: 1 === errno, perror, strerror === errno value: 2 strerror: No such file or directory perror message: No such file or directory Attempting large allocation: errno value: 12 strerror: Cannot allocate memory malloc error: Cannot allocate memory --- Menu --- ... Enter choice: 3 === Manual Validation === Error: Division by zero. Error: Index 10 out of bounds. Error: Invalid age -5. --- Menu --- ... Enter choice: 5 Exiting.
Common Pitfalls, Edge Cases & Memory Considerations
1. Not Checking Return Values
Ignoring return values can lead to undefined behavior.
c
FILE *fp = fopen("file.txt", "r"); fgetc(fp); // BUG: fp may be NULL Solution: Always check return values before using results.
2. Using errno Without Checking Function Return
errno may contain stale values from previous errors.
c
remove("file.txt"); printf("%s\n", strerror(errno)); // BUG: errno may be stale Solution: Only check errno after a function returns an error indicator.
3. Not Resetting errno
Before calling a function that sets errno, reset it to 0.
c
errno = 0; long val = strtol("123", NULL, 10); if (errno == ERANGE) { ... } Solution: Set errno = 0 before functions where error detection via errno is needed.
4. Forgetting to Close Files on Error
Early returns without closing files leak resources.
c
FILE *fp = fopen("file.txt", "r"); if (someError) { return -1; // BUG: fp not closed } Solution: Close files before returning on error paths.
5. Using feof as Loop Condition
Using feof as loop condition is an anti-pattern.
c
while (!feof(fp)) { // BUG: may process last character twice fgets(buffer, sizeof(buffer), fp); } Solution: Use the return value of the read function.
6. Not Checking malloc Return
Using malloc result without checking for NULL.
c
int *ptr = malloc(1000000000); ptr[0] = 10; // BUG: ptr may be NULL
Solution: Always check if (ptr != NULL) after allocation.
7. Division by Zero
Division by zero causes undefined behavior (crash on most systems).
c
int result = a / b; // BUG if b == 0
Solution: Check if (b != 0) before division.
8. Array Bounds Access
Accessing arrays out of bounds is undefined behavior.
c
int arr[5]; arr[10] = 42; // BUG: out of bounds
Solution: Validate indices before access.
9. Memory Leaks on Error Paths
Forgetting to free allocated memory on error paths.
c
int *ptr = malloc(100); if (someError) { return -1; // BUG: memory leak } free(ptr); Solution: Free memory before returning on error paths.
10. Silent Failures
Ignoring errors entirely leads to hard-to-debug issues.
Solution: Always report errors with descriptive messages.
Summary & Best Practices
Error handling in C relies on return values, errno, and manual validation. Proper error handling ensures robust programs that gracefully handle failures.
Key Takeaways
- Check return values of functions (
NULL,-1,EOF). - Use
errnofor error codes. - Use
perrorandstrerrorfor descriptive messages. - Use
ferrorfor file operation errors. - Use
feoffor end-of-file detection. - Use
clearerrto reset error flags. - Use
EXIT_SUCCESSandEXIT_FAILUREfor program exit. - Validate conditions manually (division by zero, bounds, input).
Best Practices
- Always check function return values.
- Use
perrororstrerror(errno)for error reporting. - Reset
errnoto 0 before functions that set it. - Close files and free memory on error paths.
- Use early returns for error handling to keep code clean.
- Validate user input before processing.
- Check for division by zero.
- Validate array indices before access.
- Use
EXIT_SUCCESSandEXIT_FAILUREfor program termination. - Test error paths to ensure proper handling.
By mastering error handling in C, you’ll write robust, reliable programs that handle failures gracefully and prevent crashes.




