Search code examples
cmemory-managementheap-memoryfree

What is a good coding practice for freeing allocated resources at failure/exit points in C?


I'm working on a school project and the teacher asked us to free all resources at program's termination.

I'm struggling to find a way to write more readable and/or less code to manage that, specifically considering this is made more complex by the fact that different exit points will probably have a different set of resources to free.

The simplest and most messy solution seems to be this(exit points are represented as "some_sys_call" calls that return -1):

char *a = malloc(1);
if(some_sys_call() == -1){
   free(a);
   return -1;
}
//b is needed until the end
char *b = malloc(1);
if(some_sys_call() == -1){
   free(a);
   free(b);
   return -1;
}
//c is needed until the end
char *c = malloc(1);
//a is no longer needed from here on, so it's freed right away
free(a);
if(some_sys_call() == -1){
   free(b);
   free(c);
   return -1;
}
//Exit point when there are no errors
free(b);
free(c);
return 0;

This doesn't seem very appealing for obvious reasons: you need to write a lot of code especially when you have a lot of resources, making the code bloated with frees and less readable. Of course you can't simply write a macro or function that frees all the resources and call it at each exit point, like this:

#define free_all  free(a); \
                  free(b); \
                  free(c);

You could technically define different macros/functions for each set of frees:

#define free_1 free(a); 

#define free_2 free(a); \
               free(b);
...

This would make the "actual" code more readable but would still require you to write a lot of different macros and doesn't seem to me like a good solution either.

Is this considered a common problem? How is this usually handled?


Solution

  • This is a well known practice in C, although if it is best or not is disputable

    char *a = malloc(1);
    char *b = NULL;
    char *c = NULL;
    int ret = 0;
    
    if(some_sys_call() == -1) {
      goto fail;
    }
    
    b = malloc(1);
    if(some_sys_call() == -1) {
      goto fail;
    }
    
    c = malloc(1);
    if(some_sys_call() == -1) {
      goto fail;
    }
    
    goto cleanup;
    
    fail:
      ret = -1;
    cleanup:
      free(c);
      free(b);
      free(a);
    
    return ret;
    

    The same but a bit shorter (inspired by proxyres /resolver_win8.c:338):

    char *a = malloc(1);
    char *b = NULL;
    char *c = NULL;
    int ret = 0;
    
    if(some_sys_call() == -1) {
      goto fail;
    }
    
    b = malloc(1);
    if(some_sys_call() == -1) {
      goto fail;
    }
    
    c = malloc(1);
    if(some_sys_call() == -1) {
    fail:
      ret = -1;
    }
    
    free(c);
    free(b);
    free(a);
    
    return ret;