Expecting the Unexpected: A Complete Guide to Error Handling in C

Unlike modern languages with exceptions, C has no built-in error handling mechanism. Instead, it relies on return values, global variables, and programmer discipline. This makes error handling in C both flexible and error-prone. Understanding how to properly handle errors is essential for writing robust, reliable C programs that can gracefully handle unexpected situations.

Why Error Handling Matters

Error handling is critical because:

  • Hardware can fail (disk full, network down)
  • User input can be invalid (wrong format, out of range)
  • Resources can be unavailable (memory allocation fails)
  • External conditions change (file deleted, permissions changed)
  • Bugs exist (null pointers, buffer overflows)

Without proper error handling, programs crash, corrupt data, or create security vulnerabilities.

Common Error Handling Techniques

C provides several approaches to error handling:

TechniqueDescriptionUse Case
Return valuesFunction returns status codeMost common approach
errno globalSet on system call failuresSystem-level errors
setjmp/longjmpNon-local jumpsException-like handling
AssertionsDebugging aidDevelopment only
Signal handlersAsynchronous eventsOS signals

1. Return Value Error Handling

The most common approach is to use return values to indicate success or failure.

#include <stdio.h>
#include <stdlib.h>
// Return 1 for success, 0 for failure
int divide(int a, int b, double *result) {
if (b == 0) {
return 0;  // Error: division by zero
}
*result = (double)a / b;
return 1;  // Success
}
int main() {
int x = 10, y = 0;
double result;
if (divide(x, y, &result)) {
printf("%d / %d = %f\n", x, y, result);
} else {
printf("Error: Division by zero!\n");
}
return 0;
}

Using Enum for Error Codes:

#include <stdio.h>
typedef enum {
SUCCESS = 0,
ERR_DIVISION_BY_ZERO,
ERR_NEGATIVE_VALUE,
ERR_OUT_OF_RANGE,
ERR_NULL_POINTER
} ErrorCode;
ErrorCode calculateSquareRoot(double value, double *result) {
if (result == NULL) {
return ERR_NULL_POINTER;
}
if (value < 0) {
return ERR_NEGATIVE_VALUE;
}
*result = sqrt(value);
return SUCCESS;
}
const char* errorToString(ErrorCode code) {
switch (code) {
case SUCCESS: return "Success";
case ERR_DIVISION_BY_ZERO: return "Division by zero";
case ERR_NEGATIVE_VALUE: return "Negative value not allowed";
case ERR_OUT_OF_RANGE: return "Value out of range";
case ERR_NULL_POINTER: return "Null pointer provided";
default: return "Unknown error";
}
}
int main() {
double value = -5.0;
double result;
ErrorCode err = calculateSquareRoot(value, &result);
if (err == SUCCESS) {
printf("Square root of %f = %f\n", value, result);
} else {
printf("Error: %s\n", errorToString(err));
}
return 0;
}

2. Using errno for System Errors

The global variable errno (defined in <errno.h>) is set by system calls and library functions on error.

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
int main() {
FILE *file = fopen("nonexistent.txt", "r");
if (file == NULL) {
// errno is set by fopen
printf("Error opening file: %s\n", strerror(errno));
// Or print the error number
printf("errno = %d\n", errno);
// Perror is a convenience function
perror("fopen");
}
// Another example: invalid number conversion
char *str = "123abc";
char *endptr;
errno = 0;  // Clear errno before call
long val = strtol(str, &endptr, 10);
if (errno != 0) {
perror("strtol");
} else if (endptr == str) {
printf("No digits found\n");
} else if (*endptr != '\0') {
printf("Partial conversion: %ld, remaining: %s\n", val, endptr);
} else {
printf("Full conversion: %ld\n", val);
}
return 0;
}

Common errno Values:

#include <stdio.h>
#include <errno.h>
int main() {
printf("Common errno values:\n");
printf("  EPERM: %d - Operation not permitted\n", EPERM);
printf("  ENOENT: %d - No such file or directory\n", ENOENT);
printf("  EACCES: %d - Permission denied\n", EACCES);
printf("  EBUSY: %d - Device or resource busy\n", EBUSY);
printf("  ENOMEM: %d - Cannot allocate memory\n", ENOMEM);
printf("  EINVAL: %d - Invalid argument\n", EINVAL);
return 0;
}

3. Resource Cleanup and RAII-like Patterns

Proper error handling must ensure resources are cleaned up:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#define CHECK(cond, cleanup, msg) \
if (!(cond)) { \
perror(msg); \
goto cleanup; \
}
int processFile(const char *filename) {
FILE *file = NULL;
int *buffer = NULL;
int result = -1;
// Open file
file = fopen(filename, "r");
CHECK(file != NULL, cleanup, "Failed to open file");
// Allocate buffer
buffer = malloc(1024);
CHECK(buffer != NULL, cleanup, "Failed to allocate memory");
// Read and process file
size_t bytesRead = fread(buffer, 1, 1024, file);
if (bytesRead == 0 && ferror(file)) {
perror("Failed to read file");
goto cleanup;
}
// Process data...
printf("Successfully read %zu bytes\n", bytesRead);
result = 0;  // Success
cleanup:
if (buffer) free(buffer);
if (file) fclose(file);
return result;
}
int main() {
if (processFile("test.txt") == 0) {
printf("File processed successfully\n");
} else {
printf("File processing failed\n");
}
return 0;
}

4. Nested Error Handling with Cleanup

For complex functions with multiple resources:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *data;
size_t size;
FILE *file;
int *temp_buffer;
} Context;
int initializeContext(Context *ctx) {
memset(ctx, 0, sizeof(Context));
return 0;  // Success
}
void cleanupContext(Context *ctx) {
if (ctx->temp_buffer) free(ctx->temp_buffer);
if (ctx->file) fclose(ctx->file);
if (ctx->data) free(ctx->data);
}
int complexOperation(const char *filename) {
Context ctx;
int result = -1;
if (initializeContext(&ctx) != 0) {
return -1;
}
// Allocate data
ctx.data = malloc(1024);
if (!ctx.data) {
perror("Failed to allocate data");
goto cleanup;
}
// Open file
ctx.file = fopen(filename, "r");
if (!ctx.file) {
perror("Failed to open file");
goto cleanup;
}
// Allocate temp buffer
ctx.temp_buffer = malloc(512 * sizeof(int));
if (!ctx.temp_buffer) {
perror("Failed to allocate temp buffer");
goto cleanup;
}
// Read from file
if (fread(ctx.data, 1, 1024, ctx.file) < 1024) {
if (feof(ctx.file)) {
printf("End of file reached\n");
} else if (ferror(ctx.file)) {
perror("Error reading file");
goto cleanup;
}
}
// Process data...
result = 0;  // Success
cleanup:
cleanupContext(&ctx);
return result;
}
int main() {
if (complexOperation("test.txt") == 0) {
printf("Operation successful\n");
} else {
printf("Operation failed\n");
}
return 0;
}

5. Setjmp/Longjmp for Exception-like Handling

setjmp and longjmp provide a way to perform non-local jumps, similar to exceptions:

#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>
jmp_buf exception_buffer;
#define TRY if ((exception_value = setjmp(exception_buffer)) == 0)
#define CATCH(x) else if (exception_value == x)
#define THROW(x) longjmp(exception_buffer, x)
enum Exceptions {
NO_ERROR = 0,
DIVISION_BY_ZERO,
MEMORY_ERROR,
FILE_ERROR
};
int exception_value;
double safe_divide(int a, int b) {
if (b == 0) {
THROW(DIVISION_BY_ZERO);
}
return (double)a / b;
}
void* safe_malloc(size_t size) {
void *ptr = malloc(size);
if (!ptr) {
THROW(MEMORY_ERROR);
}
return ptr;
}
void process_data() {
int *data = safe_malloc(100 * sizeof(int));
double result = safe_divide(10, 0);  // Will throw
// This line never executes
free(data);
}
int main() {
TRY {
printf("Trying risky operation...\n");
process_data();
}
CATCH(DIVISION_BY_ZERO) {
printf("Caught division by zero!\n");
}
CATCH(MEMORY_ERROR) {
printf("Caught memory allocation error!\n");
}
printf("Program continues...\n");
return 0;
}

Note: setjmp/longjmp should be used carefully as they can bypass stack unwinding and cause resource leaks.

6. Assertions for Debugging

Assertions are for catching programming errors during development:

#include <stdio.h>
#include <assert.h>
#include <string.h>
// Enable assertions (default)
#define NDEBUG  // Comment this out to enable assertions
void processString(const char *str) {
assert(str != NULL);  // Crash if str is NULL
assert(strlen(str) > 0);  // Crash if empty string
printf("Processing: %s\n", str);
}
int divide(int a, int b) {
assert(b != 0);  // Division by zero should never happen in correct code
return a / b;
}
// Custom assertion macro
#define ASSERT_MSG(cond, msg) \
do { \
if (!(cond)) { \
fprintf(stderr, "Assertion failed: %s, file %s, line %d\n", \
msg, __FILE__, __LINE__); \
abort(); \
} \
} while(0)
int main() {
processString("Hello");
// processString(NULL);  // Would trigger assertion
printf("10 / 2 = %d\n", divide(10, 2));
// printf("10 / 0 = %d\n", divide(10, 0));  // Would trigger assertion
int x = 5;
ASSERT_MSG(x > 0, "x must be positive");
printf("x = %d\n", x);
return 0;
}

7. Signal Handling for Asynchronous Errors

Signals handle asynchronous events like interrupts or segmentation faults:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
void signal_handler(int sig) {
switch (sig) {
case SIGINT:
printf("\nCaught Ctrl+C. Cleaning up...\n");
// Perform cleanup here
exit(0);
break;
case SIGSEGV:
printf("Segmentation fault! Exiting...\n");
exit(1);
break;
case SIGTERM:
printf("Termination requested. Cleaning up...\n");
exit(0);
break;
}
}
void setup_signal_handlers() {
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGSEGV, &sa, NULL);
// Ignore certain signals
signal(SIGPIPE, SIG_IGN);
}
int main() {
setup_signal_handlers();
printf("Program running. Press Ctrl+C to exit.\n");
int counter = 0;
while (1) {
printf("Working... %d\n", counter++);
sleep(1);
}
return 0;
}

8. Error Handling in Libraries

When writing libraries, provide clear error reporting:

#include <stdio.h>
#include <string.h>
#include <errno.h>
// Library error codes
typedef enum {
LIB_SUCCESS = 0,
LIB_ERR_INVALID_PARAM,
LIB_ERR_MEMORY,
LIB_ERR_IO,
LIB_ERR_INTERNAL
} LibError;
// Library context with error tracking
typedef struct {
LibError last_error;
char error_msg[256];
int system_errno;
} LibContext;
void lib_init(LibContext *ctx) {
ctx->last_error = LIB_SUCCESS;
ctx->error_msg[0] = '\0';
ctx->system_errno = 0;
}
void lib_set_error(LibContext *ctx, LibError err, const char *msg) {
ctx->last_error = err;
strncpy(ctx->error_msg, msg, sizeof(ctx->error_msg) - 1);
ctx->system_errno = errno;
}
const char* lib_error_string(LibContext *ctx) {
static char buffer[512];
switch (ctx->last_error) {
case LIB_SUCCESS:
return "Success";
case LIB_ERR_INVALID_PARAM:
snprintf(buffer, sizeof(buffer), "Invalid parameter: %s", ctx->error_msg);
return buffer;
case LIB_ERR_MEMORY:
snprintf(buffer, sizeof(buffer), "Memory error: %s", ctx->error_msg);
return buffer;
case LIB_ERR_IO:
snprintf(buffer, sizeof(buffer), "I/O error: %s (errno: %d - %s)", 
ctx->error_msg, ctx->system_errno, strerror(ctx->system_errno));
return buffer;
default:
return "Unknown error";
}
}
// Library function
LibError lib_process_file(LibContext *ctx, const char *filename) {
if (!ctx || !filename) {
lib_set_error(ctx, LIB_ERR_INVALID_PARAM, "Null pointer provided");
return LIB_ERR_INVALID_PARAM;
}
FILE *file = fopen(filename, "r");
if (!file) {
lib_set_error(ctx, LIB_ERR_IO, "Failed to open file");
return LIB_ERR_IO;
}
// Process file...
fclose(file);
ctx->last_error = LIB_SUCCESS;
return LIB_SUCCESS;
}
int main() {
LibContext ctx;
lib_init(&ctx);
LibError err = lib_process_file(&ctx, "nonexistent.txt");
if (err != LIB_SUCCESS) {
printf("Library error: %s\n", lib_error_string(&ctx));
}
return 0;
}

9. Practical Examples

Example 1: Robust File Reader

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
typedef struct {
char *data;
size_t size;
int error_line;
char error_msg[256];
} FileReaderResult;
FileReaderResult* read_file_safe(const char *filename) {
FileReaderResult *result = malloc(sizeof(FileReaderResult));
if (!result) {
return NULL;
}
result->data = NULL;
result->size = 0;
result->error_line = 0;
result->error_msg[0] = '\0';
FILE *file = fopen(filename, "r");
if (!file) {
snprintf(result->error_msg, sizeof(result->error_msg),
"Failed to open file: %s", strerror(errno));
result->error_line = __LINE__;
return result;
}
// Get file size
if (fseek(file, 0, SEEK_END) != 0) {
snprintf(result->error_msg, sizeof(result->error_msg),
"Failed to seek: %s", strerror(errno));
result->error_line = __LINE__;
fclose(file);
return result;
}
long file_size = ftell(file);
if (file_size < 0) {
snprintf(result->error_msg, sizeof(result->error_msg),
"Failed to get file size: %s", strerror(errno));
result->error_line = __LINE__;
fclose(file);
return result;
}
rewind(file);
// Allocate buffer
result->data = malloc(file_size + 1);
if (!result->data) {
snprintf(result->error_msg, sizeof(result->error_msg),
"Failed to allocate memory");
result->error_line = __LINE__;
fclose(file);
return result;
}
// Read file
size_t bytes_read = fread(result->data, 1, file_size, file);
if (bytes_read != file_size && ferror(file)) {
snprintf(result->error_msg, sizeof(result->error_msg),
"Failed to read file: %s", strerror(errno));
result->error_line = __LINE__;
free(result->data);
result->data = NULL;
fclose(file);
return result;
}
result->data[bytes_read] = '\0';
result->size = bytes_read;
fclose(file);
return result;
}
void free_file_result(FileReaderResult *result) {
if (result) {
free(result->data);
free(result);
}
}
int main() {
const char *filename = "test.txt";
FileReaderResult *result = read_file_safe(filename);
if (!result) {
printf("Critical error: Out of memory\n");
return 1;
}
if (result->error_msg[0] != '\0') {
printf("Error reading file at line %d: %s\n", 
result->error_line, result->error_msg);
} else {
printf("Successfully read %zu bytes from %s\n", 
result->size, filename);
printf("Content: %s\n", result->data);
}
free_file_result(result);
return 0;
}

Example 2: Configuration Parser with Error Reporting

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
typedef struct {
char key[64];
char value[256];
} ConfigEntry;
typedef struct {
ConfigEntry *entries;
int count;
int capacity;
char error_msg[256];
int error_line;
} Config;
Config* config_create() {
Config *cfg = malloc(sizeof(Config));
if (!cfg) return NULL;
cfg->entries = NULL;
cfg->count = 0;
cfg->capacity = 0;
cfg->error_msg[0] = '\0';
cfg->error_line = 0;
return cfg;
}
void config_free(Config *cfg) {
if (cfg) {
free(cfg->entries);
free(cfg);
}
}
int config_set_error(Config *cfg, const char *msg, int line) {
if (cfg) {
strncpy(cfg->error_msg, msg, sizeof(cfg->error_msg) - 1);
cfg->error_line = line;
}
return -1;
}
int config_add_entry(Config *cfg, const char *key, const char *value) {
if (!cfg || !key || !value) {
return config_set_error(cfg, "Null parameter", __LINE__);
}
// Validate key (only letters, numbers, underscore)
for (const char *p = key; *p; p++) {
if (!isalnum(*p) && *p != '_') {
return config_set_error(cfg, "Invalid character in key", __LINE__);
}
}
// Check capacity
if (cfg->count >= cfg->capacity) {
int new_capacity = cfg->capacity == 0 ? 10 : cfg->capacity * 2;
ConfigEntry *new_entries = realloc(cfg->entries, 
new_capacity * sizeof(ConfigEntry));
if (!new_entries) {
return config_set_error(cfg, "Out of memory", __LINE__);
}
cfg->entries = new_entries;
cfg->capacity = new_capacity;
}
// Add entry
strncpy(cfg->entries[cfg->count].key, key, sizeof(cfg->entries[0].key) - 1);
strncpy(cfg->entries[cfg->count].value, value, sizeof(cfg->entries[0].value) - 1);
cfg->count++;
return 0;  // Success
}
int config_load_file(Config *cfg, const char *filename) {
if (!cfg || !filename) {
return config_set_error(cfg, "Null parameter", __LINE__);
}
FILE *file = fopen(filename, "r");
if (!file) {
return config_set_error(cfg, "Failed to open file", __LINE__);
}
char line[256];
int line_num = 0;
while (fgets(line, sizeof(line), file)) {
line_num++;
// Skip empty lines and comments
char *p = line;
while (isspace(*p)) p++;
if (*p == '\0' || *p == '#') continue;
// Parse key = value
char key[64] = {0};
char value[256] = {0};
if (sscanf(line, "%63[^=] = %255[^\n]", key, value) != 2) {
snprintf(cfg->error_msg, sizeof(cfg->error_msg),
"Invalid syntax at line %d", line_num);
cfg->error_line = __LINE__;
fclose(file);
return -1;
}
// Trim whitespace from key and value
char *key_end = key + strlen(key) - 1;
while (key_end > key && isspace(*key_end)) *key_end-- = '\0';
char *val_start = value;
while (isspace(*val_start)) val_start++;
if (config_add_entry(cfg, key, val_start) != 0) {
fclose(file);
return -1;
}
}
fclose(file);
return 0;  // Success
}
const char* config_get(Config *cfg, const char *key, const char *default_value) {
for (int i = 0; i < cfg->count; i++) {
if (strcmp(cfg->entries[i].key, key) == 0) {
return cfg->entries[i].value;
}
}
return default_value;
}
int main() {
Config *cfg = config_create();
if (!cfg) {
printf("Failed to create config\n");
return 1;
}
if (config_load_file(cfg, "config.txt") != 0) {
printf("Error loading config: %s (at internal line %d)\n", 
cfg->error_msg, cfg->error_line);
} else {
printf("Config loaded successfully: %d entries\n", cfg->count);
// Query values
const char *host = config_get(cfg, "host", "localhost");
const char *port = config_get(cfg, "port", "8080");
printf("host = %s\n", host);
printf("port = %s\n", port);
}
config_free(cfg);
return 0;
}

Example 3: Network Operation with Timeout

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <signal.h>
#include <setjmp.h>
jmp_buf timeout_jmp;
int timeout_occurred = 0;
void timeout_handler(int sig) {
timeout_occurred = 1;
longjmp(timeout_jmp, 1);
}
int connect_with_timeout(const char *host, int port, int timeout_sec) {
struct sigaction sa;
struct addrinfo hints, *res, *p;
char port_str[16];
int sockfd = -1;
// Setup signal handler for timeout
sa.sa_handler = timeout_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGALRM, &sa, NULL);
// Get address info
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
snprintf(port_str, sizeof(port_str), "%d", port);
int status = getaddrinfo(host, port_str, &hints, &res);
if (status != 0) {
fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
return -1;
}
// Try each address
for (p = res; p != NULL; p = p->ai_next) {
sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (sockfd < 0) continue;
// Set timeout using setjmp/longjmp
timeout_occurred = 0;
if (setjmp(timeout_jmp) == 0) {
alarm(timeout_sec);
if (connect(sockfd, p->ai_addr, p->ai_addrlen) == 0) {
alarm(0);  // Cancel alarm
break;  // Success
}
alarm(0);  // Cancel alarm on failure
}
if (timeout_occurred) {
fprintf(stderr, "Connection timeout after %d seconds\n", timeout_sec);
close(sockfd);
sockfd = -1;
break;
}
close(sockfd);
sockfd = -1;
}
freeaddrinfo(res);
if (sockfd < 0 && !timeout_occurred) {
fprintf(stderr, "Failed to connect: %s\n", strerror(errno));
}
return sockfd;
}
int main() {
int sock = connect_with_timeout("example.com", 80, 5);
if (sock >= 0) {
printf("Connected successfully!\n");
// Send HTTP request
const char *request = "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n";
send(sock, request, strlen(request), 0);
// Receive response
char buffer[4096];
int bytes = recv(sock, buffer, sizeof(buffer) - 1, 0);
if (bytes > 0) {
buffer[bytes] = '\0';
printf("Received %d bytes\n", bytes);
}
close(sock);
} else {
printf("Connection failed\n");
}
return 0;
}

10. Error Handling Best Practices

1. Check All Return Values

// WRONG
fopen("file.txt", "r");
fread(buffer, 1, 100, file);
// CORRECT
FILE *file = fopen("file.txt", "r");
if (file == NULL) {
// handle error
}
size_t bytes = fread(buffer, 1, 100, file);
if (bytes < 100 && ferror(file)) {
// handle error
}

2. Clean Up Resources on Error

void* ptr1 = NULL;
void* ptr2 = NULL;
FILE* file = NULL;
ptr1 = malloc(100);
if (!ptr1) goto cleanup;
ptr2 = malloc(200);
if (!ptr2) goto cleanup;
file = fopen("file.txt", "r");
if (!file) goto cleanup;
// Success - use resources
cleanup:
if (file) fclose(file);
free(ptr2);
free(ptr1);
if (error) return -1;

3. Provide Meaningful Error Messages

// BAD
printf("Error!\n");
// GOOD
fprintf(stderr, "Failed to open config file '%s': %s\n", 
filename, strerror(errno));

4. Use Consistent Error Handling Patterns

// Define a consistent error handling macro
#define CHECK(cond, msg) \
if (!(cond)) { \
fprintf(stderr, "Error: %s at %s:%d\n", msg, __FILE__, __LINE__); \
goto error; \
}
// Use it consistently
CHECK(ptr != NULL, "Null pointer");
CHECK(file != NULL, "File open failed");

5. Consider Error Recovery Strategies

  • Retry: For transient errors (network timeouts)
  • Fallback: Use default values when config missing
  • Graceful degradation: Disable features that depend on failed resources
  • User notification: Inform user of errors and possible actions

Common Pitfalls

MistakeConsequenceSolution
Ignoring return valuesSilent failuresCheck all return values
Not checking errno immediatelyWrong error reportedCheck errno right after failure
Forgetting to free resourcesMemory leaksUse cleanup labels
Assuming malloc always succeedsCrash on NULLAlways check allocation
Not validating inputSecurity vulnerabilitiesValidate all input
Overlooking partial reads/writesData corruptionCheck actual bytes transferred

Error Handling Strategy Decision Tree

                    ┌─────────────────┐
│   Error Occurs  │
└────────┬────────┘
│
┌──────────────┴──────────────┐
│                             │
Can recover?                  Cannot recover?
│                             │
┌────┴────┐                  ┌────┴────┐
│         │                  │         │
Retry?   Fallback?           Log and  Abort with
│         │              continue  message
┌────┴────┐    │                  │         │
│         │    │                  │         │
Yes───┐   No──┐│             ┌─────┘         │
│    │    │   ││             │               │
Retry  Log  Use  ───────────┘               │
and    and  default                          │
retry  continue value                         │
with                                   │
reduced                                 │
function                                │
│
Inform user
and exit

Conclusion

Error handling in C requires discipline and careful planning. Unlike languages with exceptions, C forces you to be explicit about error conditions and their handling. This can make code more verbose but also more predictable.

Key principles for effective error handling:

  1. Always check return values - Never assume operations succeed
  2. Clean up resources - Use goto or nested ifs for cleanup
  3. Provide context - Include file, line, and reason in error messages
  4. Be consistent - Use the same error handling pattern throughout
  5. Consider recovery - Think about whether and how to recover
  6. Validate input - Check all external data before use
  7. Test error paths - Ensure error handling code itself is correct

Mastering error handling in C is what separates production-quality code from quick prototypes. It's the difference between a program that crashes mysteriously and one that gracefully handles unexpected situations, providing useful feedback to users and developers alike.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper