Error Handling Patterns
Introduction to Error Handling
Error handling is a critical aspect of robust C programming, providing mechanisms to detect, report, and manage unexpected situations during program execution.
Common Error Handling Techniques
1. Return Code Pattern
enum ErrorCodes {
SUCCESS = 0,
ERROR_INVALID_INPUT = -1,
ERROR_MEMORY_ALLOCATION = -2,
ERROR_FILE_NOT_FOUND = -3
};
int process_data(int* data, size_t length) {
if (data == NULL) {
return ERROR_INVALID_INPUT;
}
if (length == 0) {
return ERROR_INVALID_INPUT;
}
// Process data
return SUCCESS;
}
2. Error Logging Pattern
#include <errno.h>
#include <string.h>
void log_error(const char* function, int error_code) {
fprintf(stderr, "Error in %s: %s (Code: %d)\n",
function, strerror(error_code), error_code);
}
int file_operation(const char* filename) {
FILE* file = fopen(filename, "r");
if (file == NULL) {
log_error(__func__, errno);
return -1;
}
// File processing
fclose(file);
return 0;
}
Error Handling Strategies
Strategy |
Description |
Pros |
Cons |
Return Codes |
Use integer codes to indicate errors |
Simple, lightweight |
Limited error details |
Error Logging |
Log detailed error information |
Comprehensive debugging |
Performance overhead |
Global Error Variable |
Set global error state |
Easy to implement |
Not thread-safe |
Exception-like Handling |
Custom error management |
Flexible |
More complex implementation |
Advanced Error Handling Workflow
flowchart TD
A[Function Call] --> B{Validate Input}
B -->|Invalid| C[Set Error Code]
C --> D[Log Error]
D --> E[Return Error]
B -->|Valid| F[Execute Function]
F --> G{Operation Successful?}
G -->|No| C
G -->|Yes| H[Return Result]
Error Handling with Error Struct
typedef struct {
int code;
char message[256];
} ErrorContext;
ErrorContext global_error = {0, ""};
int divide_numbers(int a, int b, int* result) {
if (b == 0) {
global_error.code = -1;
snprintf(global_error.message,
sizeof(global_error.message),
"Division by zero attempted");
return -1;
}
*result = a / b;
return 0;
}
void handle_error() {
if (global_error.code != 0) {
fprintf(stderr, "Error %d: %s\n",
global_error.code,
global_error.message);
// Reset error
global_error.code = 0;
global_error.message[0] = '\0';
}
}
Error Handling Best Practices
- Always check return values
- Provide clear, informative error messages
- Use consistent error handling mechanisms
- Avoid silent failures
- Clean up resources in error paths
Defensive Programming Example
int safe_memory_operation(size_t size) {
// Validate memory allocation request
if (size == 0) {
fprintf(stderr, "Error: Zero-size allocation\n");
return -1;
}
void* memory = malloc(size);
if (memory == NULL) {
fprintf(stderr, "Error: Memory allocation failed\n");
return -1;
}
// Memory processing
free(memory);
return 0;
}
By implementing robust error handling strategies, developers using LabEx can create more reliable and maintainable C applications that gracefully manage unexpected scenarios.