Site Tools


c-goto-error-handling

C goto error handling

C goto error handling uses goto labels to centralize cleanup code, avoiding nested error handling and ensuring resources are freed consistently. Each operation jumps to the appropriate cleanup label on error, executing only the necessary cleanup steps in reverse order.

Use goto error_label: in C to handle cleanup elegantly without repetition or deeply nested conditionals.

Example

This example shows goto for resource cleanup with proper unwinding.

// compile: gcc -o goto_error goto_error.c
// run: ./goto_error
// description: error handling with goto for cleanup
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
typedef struct {
    FILE *f;
    int *buffer;
} Resources;
 
int read_config(const char *filename, Resources *res) {
    memset(res, 0, sizeof(*res));
 
    // Open file
    res->f = fopen(filename, "r");
    if (res->f == NULL) {
        fprintf(stderr, "Failed to open %s\n", filename);
        goto error;
    }
 
    // Allocate buffer
    res->buffer = malloc(1024 * sizeof(int));
    if (res->buffer == NULL) {
        fprintf(stderr, "Failed to allocate buffer\n");
        goto error_close_file;
    }
 
    // Read data
    int count = fread(res->buffer, sizeof(int), 100, res->f);
    if (count != 100) {
        fprintf(stderr, "Failed to read data\n");
        goto error_free_buffer;
    }
 
    return 0;  // Success
 
    // Error handling with cleanup
error_free_buffer:
    free(res->buffer);
    res->buffer = NULL;
error_close_file:
    fclose(res->f);
    res->f = NULL;
error:
    return -1;
}
 
void cleanup(Resources *res) {
    if (res->buffer != NULL) {
        free(res->buffer);
        res->buffer = NULL;
    }
    if (res->f != NULL) {
        fclose(res->f);
        res->f = NULL;
    }
}
 
int main() {
    Resources res;
 
    // Try to read config
    if (read_config("nonexistent.dat", &res) != 0) {
        printf("Failed to read config\n");
        cleanup(&res);
        return 1;
    }
 
    printf("Config read successfully\n");
    cleanup(&res);
    return 0;
}

Common patterns

Error path organization:

  • Each allocation/open gets its own cleanup label
  • Labels positioned in reverse order of allocation
  • First error jumps to closest label
  • Falls through to cleanup of prior resources

Naming convention:

  • error_resource_name:: clear what's cleaned up
  • error:: final fallback cleanup
  • Makes control flow obvious

Comparison with alternatives:

  • No goto: deeply nested if/else, duplicate cleanup
  • Goto: flat structure, cleanup centralized, efficient
  • Exceptions (C++): automatic cleanup, but C lacks this

Resource management:

  • Set pointers to NULL after freeing
  • Check for NULL before freeing again
  • Ensures idempotent cleanup (safe to call twice)

Scope alternatives (if available):

  • RAII (C++): automatic cleanup when scope exits
  • Defer (Go): execute function at end of scope
  • Context managers (Python): __exit__ cleanup
  • C has no built-in; goto pattern is idiomatic

Best practices:

  • Keep error paths simple and obvious
  • Document what each label cleans up
  • Test error paths thoroughly
  • Don't use goto for other control flow (only errors)

Avoiding goto for non-error control:

  • goto for error handling: accepted and idiomatic in C
  • goto for loops/jumps: confusing, use loops instead
  • Linux kernel uses goto heavily for error paths
  • Only structured goto (downward jumps) for errors

Error propagation:

  • Return error code from function
  • Caller decides whether to continue or unwind further
  • Chain error returns up the call stack
  • Each level cleans up its own resources

Modern C patterns:

  • Use of __attribute__((cleanup)) (GCC) to auto-cleanup
  • Defer macros (simulating Go's defer)
  • But goto error handling remains most portable
c-goto-error-handling.md · Last modified: by 127.0.0.1