C Error Handling and Resource Cleanup Pattern
An efficient C error handling and resource cleanup pattern using goto labels and standard free calls to manage memory and prevent allocation leaks.
Contents
Introduction
The C standard library function free, provided by stdlib.h, must be called to deallocate memory allocated by functions such as malloc or calloc. Proper resource management is critical in C programming to prevent memory leaks and ensure application stability. However, complex control flows with multiple failure points often introduce repetitive cleanup logic.
A structured error handling pattern solves this cleanup challenge. It consolidates all deallocation routines at the end of the function. This structure ensures reliable resource cleanup before execution returns to the caller. The approach handles errors uniformly, regardless of where they occur during processing. A standard implementation of this pattern is demonstrated below.
Implementation Notes
The first constant within an
enumautomatically evaluates to 0. Returning 0 upon success and a non-zero value upon failure is a recommended convention, enabling straightforward error validation viaif (error) ….The
retvalvariable defaults toSUCCESS. When an error occurs, the corresponding error code is assigned to this variable and subsequently returned after the execution passes through theoutlabel.The implementation defines two labels:
cleanup_outandout. When an error is encountered, control flow transfers to the appropriate label via agotostatement. This application ofgotois standard practice as it follows a predictable structural pattern and restricts jumps exclusively downward.The
freefunction performs no operation if aNULLpointer is passed. Consequently, conditional checks or distinct cleanup stages for unallocated pointers within the cleanup block are redundant.
Example Program
#include <stdio.h>
#include <stdlib.h>
typedef enum {
SUCCESS, // <--------------------------------------------------------- (1)
ERR_INVALID_SIZE,
ERR_MALLOC_FAILED
} result;
result f(const size_t size)
{
int retval = SUCCESS; // <-------------------------------------------- (2)
if (size > 1000)
{
retval = ERR_INVALID_SIZE;
goto out; // <---------------------------------------------------- (3)
}
int *m1 = malloc(sizeof(int) * size);
if (m1 == NULL)
{
retval = ERR_MALLOC_FAILED;
goto cleanup_out;
}
char *m2 = malloc(sizeof(char) * size);
if (m2 == NULL)
{
retval = ERR_MALLOC_FAILED;
goto cleanup_out;
}
cleanup_out:
free(m1); // <-------------------------------------------------------- (4)
free(m2);
out:
return retval;
}
void printerr(const result err)
{
switch (err)
{
case ERR_INVALID_SIZE:
fprintf(stderr, "Invalid size supplied.\n");
break;
case ERR_MALLOC_FAILED:
fprintf(stderr, "Error allocating memory.\n");
break;
}
}
int main(void)
{
result err = f(2000);
if (err) // An error occurred.
{
printerr(err);
return EXIT_FAILURE;
}
printf("Program completed successfully.\n");
return EXIT_SUCCESS;
}Generalized Software Pattern
The technique shown in the preceding sample code generalizes into the multi-step error handling and resource cleanup pattern demonstrated below:
result f(…)
{
result retval = SUCCESS;
result err;
resource *r1 = open_resource1();
if (err = step1())
{
retval = ERR_STEP1_FAILED;
goto cleanup_step1_out;
}
resource *r2 = open_resource2();
if (err = step2())
{
retval = ERR_STEP2_FAILED;
goto cleanup_step2_step1_out;
}
resource *r3 = open_resource3();
if (err = step3())
{
retval = ERR_STEP3_FAILED;
goto cleanup_step3_step2_step1_out;
}
cleanup_step3_step2_step1_out:
free_resource(r3);
cleanup_step2_step1_out:
free_resource(r2);
cleanup_step1_out:
free_resource(r1);
out:
return retval;
}