Error handling is the bedrock of robust C programming. Unlike modern languages with exception mechanisms, C relies on a simple but powerful system centered around errno. Mastering errno is essential for writing reliable, production-grade C code that gracefully handles failures. This comprehensive guide explores every facet of errno-based error handling, from basic usage to advanced patterns.
What is errno?
errno (error number) is a global integer variable defined in <errno.h> that system calls and library functions set to indicate what went wrong. When a function fails, it typically returns a sentinel value (like -1 or NULL) and sets errno to a specific error code.
#include <errno.h> #include <stdio.h> #include <string.h> #include <stdlib.h> extern int errno; // Actually defined in errno.h
The errno Landscape
1. Common errno Values
#include <errno.h> #include <stdio.h> void print_common_errno_values() { printf("Common errno values:\n"); printf(" EPERM : %d - Operation not permitted\n", EPERM); printf(" ENOENT : %d - No such file or directory\n", ENOENT); printf(" ESRCH : %d - No such process\n", ESRCH); printf(" EINTR : %d - Interrupted system call\n", EINTR); printf(" EIO : %d - I/O error\n", EIO); printf(" ENXIO : %d - No such device or address\n", ENXIO); printf(" E2BIG : %d - Argument list too long\n", E2BIG); printf(" ENOEXEC : %d - Exec format error\n", ENOEXEC); printf(" EBADF : %d - Bad file number\n", EBADF); printf(" ECHILD : %d - No child processes\n", ECHILD); printf(" EAGAIN : %d - Try again\n", EAGAIN); printf(" ENOMEM : %d - Out of memory\n", ENOMEM); printf(" EACCES : %d - Permission denied\n", EACCES); printf(" EFAULT : %d - Bad address\n", EFAULT); printf(" EBUSY : %d - Device or resource busy\n", EBUSY); printf(" EEXIST : %d - File exists\n", EEXIST); printf(" EXDEV : %d - Cross-device link\n", EXDEV); printf(" ENODEV : %d - No such device\n", ENODEV); printf(" ENOTDIR : %d - Not a directory\n", ENOTDIR); printf(" EISDIR : %d - Is a directory\n", EISDIR); printf(" EINVAL : %d - Invalid argument\n", EINVAL); printf(" ENFILE : %d - File table overflow\n", ENFILE); printf(" EMFILE : %d - Too many open files\n", EMFILE); printf(" ENOTTY : %d - Not a typewriter\n", ENOTTY); printf(" EFBIG : %d - File too large\n", EFBIG); printf(" ENOSPC : %d - No space left on device\n", ENOSPC); printf(" ESPIPE : %d - Illegal seek\n", ESPIPE); printf(" EROFS : %d - Read-only file system\n", EROFS); printf(" EPIPE : %d - Broken pipe\n", EPIPE); printf(" EDOM : %d - Math argument out of domain\n", EDOM); printf(" ERANGE : %d - Math result not representable\n", ERANGE); } 2. errno Categories
// Error categories by domain void categorize_errno(int err) { printf("errno %d: ", err); // File system errors if (err == ENOENT || err == EACCES || err == EPERM || err == ENOTDIR || err == EISDIR || err == EROFS || err == ENOSPC || err == EFBIG || err == EMLINK) { printf("File system error\n"); } // Resource errors else if (err == ENOMEM || err == ENFILE || err == EMFILE || err == ENOSPC || err == EBUSY) { printf("Resource error\n"); } // I/O errors else if (err == EIO || err == EAGAIN || err == EWOULDBLOCK || err == EPIPE || err == ENXIO) { printf("I/O error\n"); } // Process errors else if (err == ECHILD || err == ESRCH || err == EPERM) { printf("Process error\n"); } // Argument errors else if (err == EINVAL || err == EFAULT || err == E2BIG) { printf("Argument error\n"); } // Math errors else if (err == EDOM || err == ERANGE) { printf("Math error\n"); } // Network errors else if (err == ECONNREFUSED || err == ETIMEDOUT || err == ENETUNREACH) { printf("Network error\n"); } else { printf("Other error\n"); } } Basic errno Usage Patterns
1. The Essential Pattern
#include <stdio.h> #include <errno.h> #include <string.h> #include <stdlib.h> void basic_errno_pattern() { FILE *file = fopen("nonexistent.txt", "r"); if (file == NULL) { // Check errno immediately after failure int saved_errno = errno; // Save immediately fprintf(stderr, "Error opening file: %s (errno=%d)\n", strerror(saved_errno), saved_errno); // Handle specific errors switch (saved_errno) { case ENOENT: fprintf(stderr, " -> File doesn't exist. Creating default...\n"); // Create default file break; case EACCES: fprintf(stderr, " -> Permission denied. Check file permissions.\n"); break; case ENOMEM: fprintf(stderr, " -> Out of memory. Cannot proceed.\n"); exit(1); default: fprintf(stderr, " -> Unknown error occurred.\n"); } } else { fclose(file); } } 2. Clearing errno Before Critical Calls
void clear_errno_before_call() { errno = 0; // Clear before call long result = strtol("123abc", NULL, 10); if (errno != 0) { // Error occurred during conversion fprintf(stderr, "Conversion error: %s\n", strerror(errno)); } else { printf("Conversion successful: %ld\n", result); } } 3. perror() for Quick Error Messages
#include <stdio.h> #include <errno.h> void perror_demo() { FILE *file = fopen("/root/secret.txt", "r"); if (file == NULL) { // Prints: "Cannot open file: Permission denied" perror("Cannot open file"); // Equivalent to: // fprintf(stderr, "Cannot open file: %s\n", strerror(errno)); } } Thread Safety and errno
1. Thread-Local Storage
#include <pthread.h> #include <errno.h> #include <stdio.h> #include <unistd.h> void* thread_worker(void* arg) { int thread_num = *(int*)arg; // Each thread has its own errno FILE *file = fopen("/nonexistent", "r"); if (file == NULL) { printf("Thread %d: errno = %d (%s)\n", thread_num, errno, strerror(errno)); } // Simulate work sleep(1); // Another operation that might set errno int result = write(-1, "test", 4); // Invalid file descriptor if (result == -1) { printf("Thread %d: errno = %d (%s)\n", thread_num, errno, strerror(errno)); } return NULL; } void demonstrate_thread_safety() { pthread_t threads[3]; int ids[3] = {1, 2, 3}; for (int i = 0; i < 3; i++) { pthread_create(&threads[i], NULL, thread_worker, &ids[i]); } for (int i = 0; i < 3; i++) { pthread_join(threads[i], NULL); } } 2. errno as a Macro (POSIX)
// On modern systems, errno is a macro that expands to a thread-local value // __thread int errno; // GCC thread-local storage // #define errno (*__errno_location()) // Typical implementation #include <stdio.h> void demonstrate_errno_location() { // Get pointer to thread-local errno int *errno_ptr = __errno_location(); errno = ENOENT; printf("errno = %d, *errno_ptr = %d\n", errno, *errno_ptr); printf("Same location? %s\n", &errno == errno_ptr ? "Yes" : "No"); } Advanced Error Handling Patterns
1. The Check and Save Pattern
#include <errno.h> #include <stdio.h> #include <stdlib.h> typedef struct { int errnum; char message[256]; char function[64]; int line; } ErrorInfo; ErrorInfo last_error; void save_error(int errnum, const char *func, int line, const char *msg) { last_error.errnum = errnum; last_error.line = line; strncpy(last_error.function, func, sizeof(last_error.function) - 1); snprintf(last_error.message, sizeof(last_error.message), "%s", msg); } #define TRY(expr) \ do { \ errno = 0; \ if ((expr) < 0) { \ save_error(errno, __func__, __LINE__, "Failed at " #expr); \ return -1; \ } \ } while(0) #define TRY_PTR(expr) \ do { \ errno = 0; \ void *result = (expr); \ if (result == NULL) { \ save_error(errno, __func__, __LINE__, "Failed at " #expr); \ return NULL; \ } \ } while(0) // Example usage int copy_file_safe(const char *src, const char *dst) { FILE *source, *dest; char buffer[4096]; size_t bytes; TRY_PTR(source = fopen(src, "rb")); TRY_PTR(dest = fopen(dst, "wb")); while ((bytes = fread(buffer, 1, sizeof(buffer), source)) > 0) { if (fwrite(buffer, 1, bytes, dest) != bytes) { save_error(errno, __func__, __LINE__, "Write failed"); fclose(source); fclose(dest); return -1; } } fclose(source); fclose(dest); return 0; } void print_last_error() { if (last_error.errnum != 0) { fprintf(stderr, "Error [%s:%d]: %s\n", last_error.function, last_error.line, last_error.message); fprintf(stderr, " System error: %s (errno=%d)\n", strerror(last_error.errnum), last_error.errnum); } } 2. Error Propagation with Cleanup
#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> typedef enum { ERR_SUCCESS = 0, ERR_OPEN, ERR_READ, ERR_WRITE, ERR_CLOSE, ERR_MEMORY } ErrorCode; typedef struct { ErrorCode code; int sys_errno; char file[64]; int line; } DetailedError; DetailedError g_error; void clear_error() { memset(&g_error, 0, sizeof(DetailedError)); } void set_error(ErrorCode code, const char *file, int line) { g_error.code = code; g_error.sys_errno = errno; strncpy(g_error.file, file, sizeof(g_error.file) - 1); g_error.line = line; } #define SET_ERROR(code) set_error(code, __FILE__, __LINE__) // Resource cleanup with error handling int process_file(const char *filename) { int fd = -1; char *buffer = NULL; ssize_t bytes_read; int ret = -1; clear_error(); // Open file fd = open(filename, O_RDONLY); if (fd == -1) { SET_ERROR(ERR_OPEN); goto cleanup; } // Allocate buffer buffer = malloc(4096); if (buffer == NULL) { SET_ERROR(ERR_MEMORY); goto cleanup; } // Read from file bytes_read = read(fd, buffer, 4096); if (bytes_read == -1) { SET_ERROR(ERR_READ); goto cleanup; } // Process data (simulate) if (bytes_read == 0) { // Empty file - not an error ret = 0; goto cleanup; } // Write to stdout if (write(STDOUT_FILENO, buffer, bytes_read) != bytes_read) { SET_ERROR(ERR_WRITE); goto cleanup; } ret = 0; cleanup: // Always clean up resources if (buffer) free(buffer); if (fd != -1) close(fd); return ret; } void print_detailed_error() { if (g_error.code != ERR_SUCCESS) { const char *error_names[] = { "SUCCESS", "OPEN", "READ", "WRITE", "CLOSE", "MEMORY" }; fprintf(stderr, "Error: %s at %s:%d\n", error_names[g_error.code], g_error.file, g_error.line); if (g_error.sys_errno != 0) { fprintf(stderr, "System error: %s (errno=%d)\n", strerror(g_error.sys_errno), g_error.sys_errno); } } } 3. Retry Logic for Transient Errors
#include <errno.h> #include <unistd.h> #include <stdio.h> #define MAX_RETRIES 5 #define RETRY_DELAY_US 100000 // 100ms // Safe write with retry on EINTR and EAGAIN ssize_t safe_write(int fd, const void *buf, size_t count) { size_t total_written = 0; const char *ptr = (const char*)buf; int retries = 0; while (total_written < count && retries < MAX_RETRIES) { ssize_t written = write(fd, ptr + total_written, count - total_written); if (written == -1) { if (errno == EINTR) { // Interrupted by signal, retry immediately continue; } else if (errno == EAGAIN || errno == EWOULDBLOCK) { // Resource temporarily unavailable, wait and retry retries++; usleep(RETRY_DELAY_US); continue; } else { // Real error return -1; } } total_written += written; retries = 0; // Reset retry count on successful write } return total_written; } // Safe read with retry ssize_t safe_read(int fd, void *buf, size_t count) { size_t total_read = 0; char *ptr = (char*)buf; int retries = 0; while (total_read < count && retries < MAX_RETRIES) { ssize_t bytes_read = read(fd, ptr + total_read, count - total_read); if (bytes_read == -1) { if (errno == EINTR) { continue; } else if (errno == EAGAIN || errno == EWOULDBLOCK) { retries++; usleep(RETRY_DELAY_US); continue; } else { return -1; } } else if (bytes_read == 0) { // EOF break; } total_read += bytes_read; retries = 0; } return total_read; } System Call Error Handling
1. File Operations
#include <fcntl.h> #include <unistd.h> #include <sys/stat.h> #include <errno.h> int robust_file_copy(const char *src, const char *dst) { int src_fd = -1, dst_fd = -1; char buffer[8192]; ssize_t bytes; int ret = -1; // Open source file src_fd = open(src, O_RDONLY); if (src_fd == -1) { fprintf(stderr, "Cannot open source '%s': %s\n", src, strerror(errno)); goto cleanup; } // Open destination file with proper permissions dst_fd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (dst_fd == -1) { fprintf(stderr, "Cannot open destination '%s': %s\n", dst, strerror(errno)); goto cleanup; } // Copy data while ((bytes = read(src_fd, buffer, sizeof(buffer))) > 0) { if (safe_write(dst_fd, buffer, bytes) != bytes) { fprintf(stderr, "Write error: %s\n", strerror(errno)); goto cleanup; } } if (bytes == -1) { fprintf(stderr, "Read error: %s\n", strerror(errno)); goto cleanup; } // Ensure data is written to disk if (fsync(dst_fd) == -1) { fprintf(stderr, "fsync error: %s\n", strerror(errno)); // Continue anyway } ret = 0; cleanup: if (src_fd != -1) close(src_fd); if (dst_fd != -1) close(dst_fd); return ret; } 2. Network Operations
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include <stdio.h> int create_server_socket(int port) { int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { perror("socket"); return -1; } // Set socket options to reuse address int opt = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) { perror("setsockopt"); close(sock); return -1; } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = INADDR_ANY; if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) { perror("bind"); close(sock); return -1; } if (listen(sock, 10) == -1) { perror("listen"); close(sock); return -1; } return sock; } int accept_with_timeout(int sock, int timeout_sec) { struct timeval tv; fd_set readfds; FD_ZERO(&readfds); FD_SET(sock, &readfds); tv.tv_sec = timeout_sec; tv.tv_usec = 0; int ret = select(sock + 1, &readfds, NULL, NULL, &tv); if (ret == -1) { if (errno == EINTR) { fprintf(stderr, "select interrupted by signal\n"); return -2; } perror("select"); return -1; } else if (ret == 0) { fprintf(stderr, "Timeout waiting for connection\n"); return -3; } int client_sock = accept(sock, NULL, NULL); if (client_sock == -1) { perror("accept"); return -1; } return client_sock; } 3. Process Management
#include <sys/wait.h> #include <signal.h> #include <errno.h> pid_t robust_fork() { pid_t pid = fork(); if (pid == -1) { if (errno == EAGAIN) { fprintf(stderr, "System resource limit reached, cannot fork\n"); } else if (errno == ENOMEM) { fprintf(stderr, "Out of memory, cannot fork\n"); } else { perror("fork"); } return -1; } return pid; } int robust_waitpid(pid_t pid, int *status, int options) { int ret; while ((ret = waitpid(pid, status, options)) == -1) { if (errno == EINTR) { // Interrupted by signal, retry continue; } perror("waitpid"); return -1; } return ret; } Custom Error Handling Framework
#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdarg.h> // Error levels typedef enum { ERROR_LEVEL_DEBUG = 0, ERROR_LEVEL_INFO, ERROR_LEVEL_WARNING, ERROR_LEVEL_ERROR, ERROR_LEVEL_FATAL } ErrorLevel; // Error context typedef struct { ErrorLevel level; int errnum; char file[64]; char function[64]; int line; char message[512]; struct ErrorContext *next; } ErrorContext; // Error handler function type typedef void (*ErrorHandler)(const ErrorContext *ctx); // Global error handling state static ErrorHandler global_handler = NULL; static ErrorContext *error_stack = NULL; // Default error handler void default_error_handler(const ErrorContext *ctx) { const char *level_names[] = {"DEBUG", "INFO", "WARNING", "ERROR", "FATAL"}; fprintf(stderr, "[%s] %s:%d in %s: %s\n", level_names[ctx->level], ctx->file, ctx->line, ctx->function, ctx->message); if (ctx->errnum != 0) { fprintf(stderr, " System error: %s (errno=%d)\n", strerror(ctx->errnum), ctx->errnum); } } // Set custom error handler void set_error_handler(ErrorHandler handler) { global_handler = handler ? handler : default_error_handler; } // Push error onto stack void push_error(ErrorLevel level, const char *file, const char *func, int line, int errnum, const char *format, ...) { ErrorContext *ctx = malloc(sizeof(ErrorContext)); if (!ctx) return; ctx->level = level; ctx->errnum = errnum; strncpy(ctx->file, file, sizeof(ctx->file) - 1); strncpy(ctx->function, func, sizeof(ctx->function) - 1); ctx->line = line; va_list args; va_start(args, format); vsnprintf(ctx->message, sizeof(ctx->message), format, args); va_end(args); ctx->next = error_stack; error_stack = ctx; // Call handler for non-debug errors if (level >= ERROR_LEVEL_WARNING) { ErrorHandler handler = global_handler ? global_handler : default_error_handler; handler(ctx); } } // Pop and report errors void pop_error() { if (error_stack) { ErrorContext *ctx = error_stack; error_stack = ctx->next; free(ctx); } } // Clear all errors void clear_errors() { while (error_stack) { pop_error(); } } // Macros for easy error reporting #define LOG_DEBUG(...) \ push_error(ERROR_LEVEL_DEBUG, __FILE__, __func__, __LINE__, 0, __VA_ARGS__) #define LOG_INFO(...) \ push_error(ERROR_LEVEL_INFO, __FILE__, __func__, __LINE__, 0, __VA_ARGS__) #define LOG_WARNING(...) \ push_error(ERROR_LEVEL_WARNING, __FILE__, __func__, __LINE__, errno, __VA_ARGS__) #define LOG_ERROR(...) \ push_error(ERROR_LEVEL_ERROR, __FILE__, __func__, __LINE__, errno, __VA_ARGS__) #define LOG_FATAL(...) \ do { \ push_error(ERROR_LEVEL_FATAL, __FILE__, __func__, __LINE__, errno, __VA_ARGS__); \ exit(EXIT_FAILURE); \ } while(0) // Guard macro for function entry/exit #define FUNCTION_ENTER() LOG_DEBUG("Entering %s", __func__) #define FUNCTION_EXIT() LOG_DEBUG("Exiting %s", __func__) // Example usage int divide_numbers(int a, int b) { FUNCTION_ENTER(); if (b == 0) { LOG_ERROR("Division by zero attempted: %d / %d", a, b); FUNCTION_EXIT(); return -1; } int result = a / b; LOG_INFO("Division result: %d / %d = %d", a, b, result); FUNCTION_EXIT(); return result; } void demonstrate_error_framework() { set_error_handler(NULL); // Use default handler divide_numbers(10, 2); divide_numbers(10, 0); // This will log an error clear_errors(); } Debugging with errno
1. errno Tracing
#include <errno.h> #include <stdio.h> // Trace errno changes void trace_errno(const char *file, int line, const char *func) { static int last_errno = 0; if (errno != last_errno) { fprintf(stderr, "TRACE: %s:%d in %s - errno changed from %d (%s) to %d (%s)\n", file, line, func, last_errno, last_errno ? strerror(last_errno) : "none", errno, errno ? strerror(errno) : "none"); last_errno = errno; } } #define TRACE_ERRNO() trace_errno(__FILE__, __LINE__, __func__) void function_with_operations() { TRACE_ERRNO(); FILE *f = fopen("/nonexistent", "r"); TRACE_ERRNO(); if (f) fclose(f); int *p = malloc(1000000000); // May fail TRACE_ERRNO(); free(p); } 2. errno Assertions
#include <assert.h> #include <errno.h> #define ASSERT_ERRNO(expected) \ do { \ int _err = errno; \ if (_err != (expected)) { \ fprintf(stderr, "ASSERT_ERRNO failed at %s:%d: expected %d (%s), got %d (%s)\n", \ __FILE__, __LINE__, \ expected, expected ? strerror(expected) : "none", \ _err, _err ? strerror(_err) : "none"); \ assert(0); \ } \ } while(0) // Example void test_strtol() { errno = 0; long val = strtol("123abc", NULL, 10); // strtol sets errno to 0 on success (no error) ASSERT_ERRNO(0); errno = 0; val = strtol("999999999999999999999999", NULL, 10); ASSERT_ERRNO(ERANGE); // Should be out of range } Common Pitfalls and Solutions
1. Don't Check errno Without Checking Return Value
// WRONG errno = 0; fopen("file.txt", "r"); if (errno != 0) { // This might trigger even if fopen succeeded! perror("fopen"); } // CORRECT FILE *file = fopen("file.txt", "r"); if (file == NULL) { int saved_errno = errno; // Save immediately fprintf(stderr, "fopen failed: %s\n", strerror(saved_errno)); } else { fclose(file); } 2. Don't Use errno After Other Functions
// WRONG FILE *file = fopen("file.txt", "r"); if (file == NULL) { printf("Error: "); // printf may change errno! perror("fopen"); } // CORRECT FILE *file = fopen("file.txt", "r"); if (file == NULL) { int saved_errno = errno; // Save immediately printf("Error: %s\n", strerror(saved_errno)); } else { fclose(file); } 3. Preserve errno in Signal Handlers
#include <signal.h> #include <errno.h> #include <unistd.h> void signal_handler(int sig) { int saved_errno = errno; // Save // Do signal-safe operations const char msg[] = "Signal received\n"; write(STDERR_FILENO, msg, sizeof(msg) - 1); errno = saved_errno; // Restore } 4. Thread-Safe errno Access
// On modern systems, errno is thread-local, but be careful // with library functions that may not be thread-safe #include <pthread.h> void* thread_func(void* arg) { // Each thread has its own errno errno = 0; FILE *f = fopen("/nonexistent", "r"); if (f == NULL) { // This errno is specific to this thread printf("Thread %ld: %s\n", (long)pthread_self(), strerror(errno)); } return NULL; } Complete Example: Robust File Processing
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #define BUFFER_SIZE 4096 #define MAX_RETRIES 3 typedef struct { char *data; size_t size; int error; int sys_errno; } FileResult; // Clear error state void file_result_clear(FileResult *result) { result->data = NULL; result->size = 0; result->error = 0; result->sys_errno = 0; } // Read entire file with robust error handling FileResult read_file_robust(const char *filename) { FileResult result; file_result_clear(&result); int fd = -1; char *buffer = NULL; size_t total = 0; size_t capacity = BUFFER_SIZE; int retries = 0; // Open file fd = open(filename, O_RDONLY); if (fd == -1) { result.error = 1; result.sys_errno = errno; return result; } // Allocate initial buffer buffer = malloc(capacity); if (buffer == NULL) { result.error = 1; result.sys_errno = ENOMEM; close(fd); return result; } // Read loop with retry on interrupt while (1) { ssize_t bytes_read = read(fd, buffer + total, capacity - total); if (bytes_read == -1) { if (errno == EINTR && retries < MAX_RETRIES) { retries++; continue; } result.error = 1; result.sys_errno = errno; free(buffer); close(fd); return result; } if (bytes_read == 0) { break; // EOF } total += bytes_read; retries = 0; // Reset retries on successful read // Expand buffer if needed if (total == capacity) { capacity *= 2; char *new_buffer = realloc(buffer, capacity); if (new_buffer == NULL) { result.error = 1; result.sys_errno = ENOMEM; free(buffer); close(fd); return result; } buffer = new_buffer; } } // Trim to actual size char *final_buffer = realloc(buffer, total + 1); if (final_buffer == NULL) { final_buffer = buffer; // Keep original if realloc fails } final_buffer[total] = '\0'; result.data = final_buffer; result.size = total; result.error = 0; close(fd); return result; } // Write file with fsync guarantee int write_file_robust(const char *filename, const char *data, size_t size) { int fd = -1; ssize_t written; size_t total = 0; int ret = -1; fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { fprintf(stderr, "Cannot open '%s': %s\n", filename, strerror(errno)); return -1; } while (total < size) { written = write(fd, data + total, size - total); if (written == -1) { if (errno == EINTR) { continue; } fprintf(stderr, "Write error: %s\n", strerror(errno)); goto cleanup; } total += written; } // Ensure data is on disk if (fsync(fd) == -1) { fprintf(stderr, "fsync error: %s\n", strerror(errno)); // Continue anyway - data may still be written } ret = 0; cleanup: close(fd); return ret; } // Process a file with comprehensive error handling int process_file(const char *input, const char *output) { FileResult read_result; int ret = -1; // Read input read_result = read_file_robust(input); if (read_result.error) { fprintf(stderr, "Failed to read '%s': ", input); if (read_result.sys_errno) { fprintf(stderr, "%s", strerror(read_result.sys_errno)); } else { fprintf(stderr, "Unknown error"); } fprintf(stderr, "\n"); return -1; } printf("Read %zu bytes from '%s'\n", read_result.size, input); // Process data (simple transformation - convert to uppercase) for (size_t i = 0; i < read_result.size; i++) { if (read_result.data[i] >= 'a' && read_result.data[i] <= 'z') { read_result.data[i] -= 32; } } // Write output if (write_file_robust(output, read_result.data, read_result.size) == -1) { fprintf(stderr, "Failed to write to '%s'\n", output); goto cleanup; } printf("Wrote %zu bytes to '%s'\n", read_result.size, output); ret = 0; cleanup: free(read_result.data); return ret; } int main(int argc, char *argv[]) { if (argc != 3) { fprintf(stderr, "Usage: %s <input> <output>\n", argv[0]); return 1; } if (process_file(argv[1], argv[2]) == -1) { return 1; } printf("File processed successfully\n"); return 0; } Best Practices Summary
| Practice | Why It Matters |
|---|---|
| Check return values before errno | errno may be set even on success |
| Save errno immediately | Other functions may change it |
| Clear errno before critical calls | Avoid false positives |
| Use thread-local errno | Modern systems are thread-safe |
| Provide context in error messages | Include filename, operation details |
| Use perror() for quick debugging | Simple, standardized output |
| Handle EINTR properly | System calls can be interrupted |
| Implement retry logic | Transient errors can succeed later |
| Clean up resources on error | Prevent resource leaks |
| Document error conditions | Help API users |
Conclusion
Error handling with errno in C is a fundamental skill that separates novice from expert C programmers. While the mechanism is simple, using it effectively requires understanding:
- Which functions set errno and how they indicate failure
- Thread-safety considerations
- Proper patterns for saving, checking, and restoring errno
- Handling transient errors with retry logic
- Building comprehensive error reporting frameworks
By following the patterns and practices outlined in this guide, you can build C applications that not only handle errors gracefully but also provide meaningful diagnostics for debugging and maintenance. Remember: robust error handling isn't an afterthought—it's a core design consideration that should be integrated from the earliest stages of development.
Complete C Programming Guide + Compilers Collection
1. C srand() Function – Understanding Seed Initialization
https://macronepal.com/understanding-the-c-srand-function
Explains how srand() initializes the pseudo-random number generator in C by setting a seed value. Using the same seed produces the same sequence, while time(NULL) gives different results each run.
2. C rand() Function Mechanics and Limitations
https://macronepal.com/c-rand-function-mechanics-and-limitations
Explains how rand() generates pseudo-random numbers between 0 and RAND_MAX, its deterministic nature, and limitations for security use cases.
3. C log() Function
https://macronepal.com/c-log-function-2
Covers natural logarithm calculation using <math.h> and its applications.
4. Mastering Date and Time in C
https://macronepal.com/mastering-date-and-time-in-c
Explains <time.h> functions like time(), clock(), difftime(), and struct tm.
5. Mastering time_t Type in C
https://macronepal.com/mastering-the-c-time_t-type-for-time-management
Explains time representation as seconds since Unix epoch and conversion functions.
6. C exp() Function
https://macronepal.com/c-exp-function-mechanics-and-implementation
Explains exponential function exp(x) and its scientific applications.
7. C log() Function (Alternate Guide)
https://macronepal.com/c-log-function
Comparison of log() and log10() with usage examples.
8. C log10() Function
https://macronepal.com/mastering-the-log10-function-in-c
Explains base-10 logarithm for engineering and scientific applications.
9. C tan() Function
https://macronepal.com/understanding-the-c-tan-function
Explains tangent function and radian-based calculations.
10. Random Numbers in C (Secure vs Predictable)
https://macronepal.com/mastering-c-random-numbers-for-secure-and-predictable-applications
Explains difference between rand() and secure randomness methods.
11. Free Online C Compiler
https://macronepal.com/free-online-c-code-compiler-2
Browser-based compiler for testing C programs instantly.
C Functions, Arguments, Parameters & Flow
Mastering Functions in C – Complete Guide
https://macronepal.com/c/mastering-functions-in-c-a-complete-guide/
Covers function structure, modular programming, and real-world usage.
Function Arguments in C
https://macronepal.com/c-function-arguments/
Explains how arguments are passed and used in function calls.
Function Parameters in C
https://macronepal.com/c-function-parameters/
Explains defining inputs for functions and matching them with arguments.
Function Declarations in C
https://macronepal.com/c-function-declarations-syntax-rules-and-best-practices/
Covers prototypes, syntax rules, and best practices.
Function Calls in C
https://macronepal.com/understanding-function-calls-in-c-syntax-mechanics-and-best-practices/
Explains execution flow and parameter handling during function calls.
Void Functions in C
https://macronepal.com/understanding-void-functions-in-c-syntax-patterns-and-best-practices/
Explains functions that do not return values.
Return Values in C
https://macronepal.com/c-return-values-mechanics-types-and-best-practices/
Explains different return types and how functions return results.
Pass-by-Value in C
https://macronepal.com/aws/understanding-pass-by-value-in-c-mechanics-implications-and-best-practices/
Explains how copies of variables are passed into functions.
Pass-by-Reference in C
https://macronepal.com/c/understanding-pass-by-reference-in-c-pointers-semantics-and-safe-practices/
Explains using pointers to modify original variables.
C strstr() Function
https://macronepal.com/aws/c-strstr-function/
Explains substring search inside strings in C.
C Preprocessor & Macros
https://macronepal.com/mastering-c-variadic-macros-for-flexible-debugging/
https://macronepal.com/mastering-the-stdc-macro-in-c/
https://macronepal.com/c-time-macro-mechanics-and-usage/
https://macronepal.com/understanding-the-c-date-macro/
https://macronepal.com/c-file-type/
https://macronepal.com/mastering-c-line-macro-for-debugging-and-diagnostics/
https://macronepal.com/mastering-predefined-macros-in-c/
https://macronepal.com/c-error-directive-mechanics-and-usage/
https://macronepal.com/understanding-the-c-pragma-directive/
https://macronepal.com/c-include-directive/
C Structures, Memory, Scope & Linkage
https://macronepal.com/mastering-structures-in-c/
https://macronepal.com/c-structure-declaration-mechanics-and-usage/
https://macronepal.com/c-structure-initialization-mechanics-and-best-practices/
https://macronepal.com/mastering-c-structure-member-access-for-reliable-data-handling/
https://macronepal.com/c-nested-structures/
https://macronepal.com/mastering-arrays-of-structures-in-c/
https://macronepal.com/c-structure-pointers-mechanics-and-implementation/
https://macronepal.com/understanding-c-structure-parameter-passing-mechanics/
https://macronepal.com/mastering-c-returning-structures-for-efficient-data-flow/
https://macronepal.com/c-self-referential-structures/
https://macronepal.com/mastering-structure-alignment-in-c/
https://macronepal.com/c-structure-padding-mechanics-and-optimization/
https://macronepal.com/understanding-c-flexible-array-members-mechanics-and-usage/
https://macronepal.com/mastering-c-anonymous-structures-for-flattened-data-layouts/
https://macronepal.com/c-unions/
https://macronepal.com/mastering-c-name-mangling-and-symbol-decoration/
https://macronepal.com/c-no-linkage-mechanics-and-scope-isolation/
https://macronepal.com/understanding-c-internal-linkage-mechanics-and-architecture/
C Scope, Storage Classes & Typedef
https://macronepal.com/mastering-function-prototype-scope-in-c/
https://macronepal.com/c-function-scope-mechanics-and-visibility/
https://macronepal.com/understanding-c-file-scope-mechanics-and-architecture/
https://macronepal.com/mastering-c-scope-rules-for-predictable-name-resolution/
https://macronepal.com/c-scope-rules/
https://macronepal.com/mastering-c-register-storage-class-for-historical-context-and-modern-alternatives/
https://macronepal.com/mastering-_thread_local-in-c/
https://macronepal.com/c-extern-storage-class-mechanics-and-usage/
https://macronepal.com/understanding-the-c-static-storage-class-mechanics-and-usage/
https://macronepal.com/c-auto-storage-class/
https://macronepal.com/c-typedef-with-pointers/
Extra Articles
https://macronepal.com/13757-2/
https://macronepal.com/13748-2/
https://macronepal.com/13747-2/
https://macronepal.com/13746-2/
https://macronepal.com/13745-2/
https://macronepal.com/13708-2/
https://macronepal.com/13707-2/
https://macronepal.com/13702-2/
Online Compilers
https://macronepal.com/free-html-online-code-compiler/
https://macronepal.com/free-online-python-code-compiler/
https://macronepal.com/free-online-python2-code-compiler/
https://macronepal.com/free-online-java-code-compiler/
https://macronepal.com/free-online-javascript-code-compiler/
https://macronepal.com/free-online-node-js-code-compiler/
https://macronepal.com/free-online-c-code-compiler/
https://macronepal.com/free-online-c-code-compiler-2/
https://macronepal.com/free-online-c-code-compiler-3/
https://macronepal.com/free-online-php-code-compiler/
https://macronepal.com/free-online-ruby-code-compiler/
https://macronepal.com/free-online-perl-code-compiler/
https://macronepal.com/free-online-lua-code-compiler/
https://macronepal.com/free-online-tcl-code-compiler/
https://macronepal.com/free-online-groovy-code-compiler/
https://macronepal.com/free-online-j-shell-code-compiler/
https://macronepal.com/free-online-haskell-code-compiler/
https://macronepal.com/free-online-scala-code-compiler/
https://macronepal.com/free-online-common-lisp-code-compiler/
https://macronepal.com/free-online-d-code-compiler/
https://macronepal.com/free-online-ada-code-compiler/
https://macronepal.com/free-erlang-code-compiler/
https://macronepal.com/free-online-assembly-code-compiler/
