C Error Handling

Unlike languages like Python or Java, C has no built-in exception handling system. There is no try-catch in C. Instead, C programs detect and handle errors through return values, the errno variable, and standard error-reporting functions. Learning to handle errors correctly makes your C programs reliable, predictable, and professional.

Why Error Handling Matters

Imagine a program that opens a file, reads data, and processes it. If the file does not exist and the program does not check for this, it will crash or produce wrong results silently. Proper error handling catches these situations and responds appropriately — showing a message, retrying, or exiting cleanly.

Method 1: Return Values

The most common error-handling method in C is checking the return value of functions. Most C standard library functions return a special value (like -1, NULL, or 0) when something goes wrong.

Example: Checking File Open Failure

#include <stdio.h>

int main()
{
    FILE *fp = fopen("data.txt", "r");

    if (fp == NULL)   // NULL means fopen failed
    {
        printf("Error: Cannot open file.\n");
        return 1;   // return non-zero to indicate failure
    }

    printf("File opened successfully.\n");
    fclose(fp);
    return 0;   // return 0 means success
}

Return Value Conventions

Function CategorySuccess ReturnError Return
File functions (fopen)Valid pointerNULL
Memory functions (malloc)Valid pointerNULL
System calls (read, write)Number of bytes-1
Math functionsResult valueSpecific error value
main() / exit code0Non-zero

Method 2: errno — The Error Number Variable

When a library function fails, it sets a global variable called errno to a specific integer code that tells you what went wrong. Include <errno.h> to use it.

#include <stdio.h>
#include <errno.h>

int main()
{
    FILE *fp = fopen("nonexistent.txt", "r");

    if (fp == NULL)
    {
        printf("errno value: %d\n", errno);   // prints the error code
        return 1;
    }

    fclose(fp);
    return 0;
}

Common errno Values

errno CodeMacro NameMeaning
1EPERMOperation not permitted
2ENOENTNo such file or directory
12ENOMEMOut of memory
13EACCESPermission denied
22EINVALInvalid argument
28ENOSPCNo space left on device

Method 3: perror() — Print Error Message

perror() prints a human-readable error message for the current value of errno. It automatically appends the system's description of the error.

#include <stdio.h>

int main()
{
    FILE *fp = fopen("missing.txt", "r");

    if (fp == NULL)
    {
        perror("fopen failed");   // prints: fopen failed: No such file or directory
        return 1;
    }

    fclose(fp);
    return 0;
}

Output:

fopen failed: No such file or directory

Method 4: strerror() — Get Error String

strerror(errno) returns the error description as a string. Use it when you want to include the error message in a custom log or format.

#include <stdio.h>
#include <errno.h>
#include <string.h>

int main()
{
    FILE *fp = fopen("missing.txt", "r");

    if (fp == NULL)
    {
        printf("Error %d: %s\n", errno, strerror(errno));
        // Output: Error 2: No such file or directory
        return 1;
    }

    fclose(fp);
    return 0;
}

Method 5: assert() — Catch Logic Bugs

assert(condition) checks a condition at runtime. If the condition is false, the program prints an error message and terminates. It is used to catch programming mistakes during development.

#include <stdio.h>
#include <assert.h>

int divide(int a, int b)
{
    assert(b != 0);   // crash with message if b is 0
    return a / b;
}

int main()
{
    printf("%d\n", divide(10, 2));   // Output: 5
    printf("%d\n", divide(10, 0));   // Assertion failed: b != 0
    return 0;
}

Assertions are disabled when you compile with -DNDEBUG, so they add zero cost in production builds.

Method 6: exit() with Status Codes

exit() terminates the program and returns a status code to the operating system.

#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp = fopen("config.txt", "r");

    if (fp == NULL)
    {
        fprintf(stderr, "Critical error: config.txt not found.\n");
        exit(EXIT_FAILURE);   // exit code 1 — indicates failure
    }

    // ... process file ...
    fclose(fp);
    exit(EXIT_SUCCESS);   // exit code 0 — indicates success
}
ConstantValueMeaning
EXIT_SUCCESS0Program ended successfully
EXIT_FAILURE1Program ended due to an error

Writing to stderr

Error messages should go to stderr (standard error), not stdout (standard output). This allows users and scripts to separate normal output from error messages.

#include <stdio.h>

int main()
{
    int age = -5;

    if (age < 0)
    {
        fprintf(stderr, "Error: Age cannot be negative (%d)\n", age);
        return 1;
    }

    printf("Age: %d\n", age);
    return 0;
}

Error Handling Pattern: Check Every Function Call

#include <stdio.h>
#include <stdlib.h>

int main()
{
    // Step 1: Open file — check for failure
    FILE *fp = fopen("data.txt", "r");
    if (!fp) { perror("Cannot open data.txt"); return 1; }

    // Step 2: Allocate memory — check for failure
    int *buffer = (int*) malloc(100 * sizeof(int));
    if (!buffer) {
        fprintf(stderr, "Memory allocation failed\n");
        fclose(fp);   // clean up already-opened file
        return 1;
    }

    // Step 3: Use file and buffer safely
    printf("File opened and memory allocated.\n");

    // Step 4: Clean up (always happens)
    free(buffer);
    fclose(fp);
    return 0;
}

Error Handling Flow Diagram


Function call
      |
      v
   Success? ──YES──> Continue program normally
      |
      NO
      |
      v
  Set errno (automatic for system functions)
      |
      v
Check return value (NULL / -1 / 0)
      |
      v
Decide response:
  - perror() / strerror()  → print message
  - return error code      → let caller handle
  - exit(EXIT_FAILURE)     → terminate cleanly
  - assert()               → crash during development

Summary

C handles errors through return values, the errno variable, and standard error-reporting functions. Every function call that can fail must be checked — especially file operations, memory allocation, and system calls. The errno variable is automatically set by library functions when an error occurs. perror() prints a human-readable error description using the current errno value, while strerror(errno) returns the error string for custom formatting. assert() catches programming logic bugs during development. Error messages belong on stderr, not stdout. Always clean up resources like files and heap memory before exiting, even when an error occurs. Disciplined error handling is the difference between a program that crashes silently and one that fails safely and informatively.

Leave a Comment

Your email address will not be published. Required fields are marked *