Without exceptions, throw, or try/catch, C functions signal failure through their return value — commonly NULL for a function returning a pointer, -1 for a function returning an otherwise-nonnegative int, or an explicit status/error code. Many standard library functions additionally set the global errno variable to a specific error code on failure, which strerror() can translate into a human-readable message. Critically, nothing forces a caller to check any of this — an ignored error return simply lets execution continue with invalid data or a null pointer, silently, until something else breaks later.
Checking return values: entirely manual, entirely optional
Nothing forces this check to happen — skip it, and a failed fopen() silently returns NULL, which then crashes the program the instant something tries to read from it, often several lines away from the actual mistake.
FILE *f = fopen("data.txt", "r");
if (f == NULL) { // MUST check manually — nothing does this for you
perror("fopen failed"); // prints a message using errno's current value
return 1;
}
// safe to use f from hereerrno and strerror: more detail on WHY it failed
errno alone is just a numeric code; strerror() translates it into a readable message — always check errno immediately after the failing call, since a subsequent successful function call can overwrite it.
#include <errno.h>
#include <string.h>
FILE *f = fopen("missing.txt", "r");
if (f == NULL) {
printf("Error: %s\n", strerror(errno)); // e.g. "No such file or directory"
}A common pattern: an explicit status/result type
Returning a dedicated enum status alongside output through a pointer parameter is a common idiom for functions that genuinely can't use a sentinel value (like -1 or NULL) to mean "failure," because every possible return value is a legitimate success case.
typedef enum { OK, ERR_DIVIDE_BY_ZERO } Status;
Status safeDivide(int a, int b, int *result) {
if (b == 0) {
return ERR_DIVIDE_BY_ZERO;
}
*result = a / b;
return OK;
}
int result;
if (safeDivide(10, 0, &result) != OK) {
printf("division failed\n");
}