C APIs report failure through return values, sentinel values, output parameters, errno, or a combination. The caller must know which mechanism applies and check it before using output. errno is meaningful only when the called function documents setting it and the return value indicates failure.
Unlike C++ or Java, C has no built-in exception mechanism. Functions report failure through return values, status flags, and the errno convention:
The most fundamental C error handling pattern: check the return value of every function that can fail.
#include <stdio.h>
#include <stdlib.h>
// Function returns -1 on error, result on success
int divide(int a, int b, int *result) {
if (b == 0) {
return -1; // error code
}
*result = a / b;
return 0; // success
}
int main() {
int result;
// Success case
if (divide(10, 2, &result) == 0) {
printf("10 / 2 = %d\n", result);
} else {
printf("Error: division failed\n");
}
// Error case
if (divide(10, 0, &result) == 0) {
printf("10 / 0 = %d\n", result);
} else {
printf("Error: cannot divide by zero\n");
}
// malloc returns NULL on failure
int *arr = malloc(1000000000ULL * sizeof *arr); // deliberately large request
if (arr == NULL) {
fprintf(stderr, "Error: memory allocation failed\n");
return EXIT_FAILURE;
}
free(arr);
return EXIT_SUCCESS;
}
errno is set by system calls when they fail. Always check errno immediately after a failed call - the next function call may overwrite it.
| errno Code | Value | Meaning |
|---|---|---|
| ENOENT | 2 | No such file or directory |
| EACCES | 13 | Permission denied |
| ENOMEM | 12 | Out of memory |
| EINVAL | 22 | Invalid argument |
| ERANGE | 34 | Result out of range |
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main() {
// Try to open a file that doesn't exist
FILE *fp = fopen("nonexistent.txt", "r");
if (fp == NULL) {
// errno is set by fopen on failure
printf("errno value: %d\n", errno);
// perror: prints "prefix: error message" to stderr
perror("fopen failed");
// strerror: returns the error string
printf("Error: %s\n", strerror(errno));
}
// Reset errno before next call
errno = 0;
// Math error: log of negative number
#include <math.h>
double result = sqrt(-1.0);
if (errno == EDOM) {
perror("sqrt(-1)"); // sqrt(-1): Numerical argument out of domain
}
return 0;
}
/*
Output:
errno value: 2
fopen failed: No such file or directory
Error: No such file or directory
*/
A clean pattern for larger programs: define your own error codes and a centralized error handler.
#include <stdio.h>
#include <stdlib.h>
// Custom error codes
typedef enum {
ERR_OK = 0,
ERR_NULL_PTR = 1,
ERR_DIV_ZERO = 2,
ERR_OVERFLOW = 3,
ERR_IO = 4
} ErrorCode;
// Error message lookup
const char* errorMessage(ErrorCode code) {
switch (code) {
case ERR_OK: return "Success";
case ERR_NULL_PTR: return "Null pointer";
case ERR_DIV_ZERO: return "Division by zero";
case ERR_OVERFLOW: return "Integer overflow";
case ERR_IO: return "I/O error";
default: return "Unknown error";
}
}
// Function using custom error codes
ErrorCode safeDivide(int a, int b, int *out) {
if (out == NULL) return ERR_NULL_PTR;
if (b == 0) return ERR_DIV_ZERO;
*out = a / b;
return ERR_OK;
}
int main() {
int result;
ErrorCode err;
err = safeDivide(10, 2, &result);
if (err == ERR_OK) printf("10/2 = %d\n", result);
else printf("Error: %s\n", errorMessage(err));
err = safeDivide(10, 0, &result);
if (err == ERR_OK) printf("10/0 = %d\n", result);
else printf("Error: %s\n", errorMessage(err)); // Error: Division by zero
err = safeDivide(10, 2, NULL);
if (err == ERR_OK) printf("result = %d\n", result);
else printf("Error: %s\n", errorMessage(err)); // Error: Null pointer
return 0;
}
Acquire resources in a clear order and release acquired resources in reverse order. A single cleanup label can be appropriate in C when several failure paths own different subsets; initialize handles to safe values so cleanup is idempotent and never releases an unowned resource.
Preserve the original error before cleanup if later functions can change errno. Return stable application status codes rather than exposing platform messages as the only contract. Log enough context to diagnose the failed operation without printing credentials or sensitive input.
Explore 500+ free tutorials across 20+ languages and frameworks.