Search Knowledge

© 2026 LIBREUNI PROJECT

C Programming Mastery / Robust Code

Error Handling and Defensive Programming

The Philosophy of Failure

C has no try-catch blocks or native exception handling. In the C model, errors are ordinary values. They are not exceptional events that interrupt the flow; they are expected outcomes that must be checked and handled explicitly by the programmer.

Patterns of Error Signaling

There are three main ways a C function signals failure:

1. Integer Return Codes

Functions return an int. Typically, 0 means success, and negative values relate to specific error types.

if (calculate_physics() != 0) {
    handle_error();
}

2. Sentinel Values (NULL)

Functions that return pointers return NULL to signify failure (e.g., malloc, fopen).

3. The Global errno

Found in <errno.h>. When a system level function fails, it sets a global integer variable errno. You can translate this number into a human-readable string using strerror().

Warning: errno is only valid immediately after a failed call. Many functions do not clear errno on success, so a successful call won’t overwrite a previous error.

The goto for Cleanup

While generally discouraged, the goto statement is widely considered “the right way” to handle cleanup in complex functions with multiple failure points. This prevents the “Arrow Anti-pattern” of nested if statements.

int process_file() {
    int cleanup_needed = 0;
    if (open_file() != 0) goto fail;
    cleanup_needed = 1;

    if (allocate_buffer() != 0) goto fail;
    
    // ... logic ...
    return 0;

fail:
    if (cleanup_needed) close_file();
    return -1;
}

Advanced: setjmp and longjmp

Found in <setjmp.h>, this is C’s version of a “Non-local Goto.” It allows you to jump directly from a deeply nested function back up to a previous state in the call stack.

  • setjmp(env): Saves the current CPU state (registers, stack pointer).
  • longjmp(env, val): Restores that state, making it look like setjmp just returned val.

Use Case: This is how basic exception handling is implemented in low-level frameworks.

Interactive Lab

Error Conversion

#include <string.h>
#include <errno.h>
// How to get a string for the current error?
char *msg = (errno);

Defensive Programming

Robust C code assumes inputs are malicious and functions will fail.

  • Asserts: Use assert(ptr != NULL); from <assert.h> to catch logic errors during development.
  • Bounds Checking: Always verify array indices before access.
  • Sanitizers: Use tools like AddressSanitizer (ASan) to find memory errors that don’t immediately crash the program.
Runtime Environment

Interactive Lab

1#include <stdio.h>
2#include <stdlib.h>
3#include <errno.h>
4 
5int main() {
6 // Intentional error: dividing by zero isn't caught by errno,
7 // but we can catch it with logic.
8 int a = 10, b = 0;
9 if (b == 0) {
10 fprintf(stderr, "Fatal Error: Division by zero avoided.\n");
11 return EXIT_FAILURE;
12 }
13 printf("Result: %d\n", a / b);
14 return EXIT_SUCCESS;
15}
System Console

Waiting for signal...