A structured lesson workspace with readable content, hands-on examples, and a clean path to completion.
C has no exceptions. No try-catch. No automatic stack unwinding. Every function that can fail must report failure through its return value, an output parameter, or the global errno variable. Every caller must check. Miss one check and you proceed with garbage data, a NULL pointer, or a corrupted state. Error handling in C is not a language feature. It is a discipline you enforce on yourself, line by line, function by function.
#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <math.h>int main(void) {FILE *fp = fopen("missing.txt", "r");if (fp == NULL) {printf("Return value: NULL\n");printf("errno: %d (%s)\n", errno, strerror(errno));}errno = 0;double result = sqrt(-1.0);if (errno == EDOM) {printf("\nsqrt(-1): domain error (errno=%d)\n", errno);printf("result: %f (isnan=%d)\n", result, isnan(result));}char *endptr;long val = strtol("abc", &endptr, 10);if (endptr == "abc") {printf("\nstrtol(\"abc\"): no conversion (val=%ld)\n", val);}return 0;}
Three mechanisms. fopen reports failure via NULL return and sets errno. sqrt reports via errno and returns NaN. strtol reports via the endptr output parameter. Each function chooses its own convention. You must read the documentation for every function you call and check the right indicator. There is no uniform error protocol in C.
#include <stdio.h>#include <stdlib.h>#include <string.h>int process_file(const char *path) {int result = -1;char *buffer = NULL;FILE *fp = NULL;fp = fopen(path, "r");if (fp == NULL) {perror("fopen");goto cleanup;}buffer = malloc(1024);if (buffer == NULL) {fprintf(stderr, "malloc failed\n");goto cleanup;}size_t n = fread(buffer, 1, 1023, fp);buffer[n] = '\0';printf("Read %zu bytes: %.40s...\n", n, buffer);result = 0;cleanup:free(buffer);if (fp) fclose(fp);return result;}int main(void) {printf("Status: %d\n", process_file("notes.txt"));printf("Status: %d\n", process_file("missing.txt"));return 0;}
The goto cleanup pattern centralizes resource release. Every failure path jumps to the same cleanup label. free(NULL) is safe. fclose is guarded by the NULL check. No matter where the function fails, the cleanup code runs exactly once. This is the standard pattern in the Linux kernel, in OpenSSL, and in every C codebase that manages multiple resources. It is not spaghetti. It is structured error recovery.
The goto cleanup pattern is the one legitimate use of goto in C. Do not use goto for arbitrary jumps, loop control, or forward skips. The cleanup pattern has a specific structure: forward-only jumps to a single label at the bottom of the function. Violating this structure turns the code into the spaghetti that gave goto its reputation.
Error handling in C is manual, verbose, and non-negotiable. Check every return value. Document your error conventions. Use goto cleanup for multi-resource functions. The code that handles errors well is longer than the code that ignores them. It is also the code that does not crash at 3 AM on a Friday. Next: 2D array basics, revisiting multi-dimensional arrays with the full pointer knowledge you now have.
Finish the lesson once you have worked through the material. This awards ★ 30 XP.