Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best way to free memory after returning from an error?

Suppose I have a function that allocates memory for the caller:

int func(void **mem1, void **mem2) {
    *mem1 = malloc(SIZE);
    if (!*mem1) return 1;

    *mem2 = malloc(SIZE);
    if (!*mem2) {
        /* ... */
        return 1;
    }

    return 0;
}

I'd like to hear your feedback on the best way to free() the allocated memory in case the second malloc() fails. You can imagine a more elaborate situation with more error exit points and more allocated memory.

like image 271
Andrew Keeton Avatar asked Feb 20 '09 16:02

Andrew Keeton


3 Answers

I know people are loathe to use them, but this is the perfect situation for a goto in C.

int func( void** mem1, void** mem2 )
{
    int retval = 0;
    *mem1 = malloc(SIZE);
    if (!*mem1) {
        retval = 1;
        goto err;
    }

    *mem2 = malloc(SIZE);
    if (!*mem2) {
        retval = 1;
        goto err;
    }
// ...     
    goto out;
// ...
err:
    if( *mem1 ) free( *mem1 );
    if( *mem2 ) free( *mem2 );
out:
    return retval;
}      
like image 126
FreeMemory Avatar answered Oct 21 '22 00:10

FreeMemory


This is a bit controversial, but I think the goto approach used in Linux kernel actually works pretty well in this situation:

int get_item(item_t* item)
{
  void *mem1, *mem2;
  int ret=-ENOMEM;
  /* allocate memory */
  mem1=malloc(...);
  if(mem1==NULL) goto mem1_failed;

  mem2=malloc(...);
  if(mem2==NULL) goto mem2_failed;

  /* take a lock */
  if(!mutex_lock_interruptible(...)) { /* failed */
    ret=-EINTR;
    goto lock_failed;
  }

  /* now, do the useful work */
  do_stuff_to_acquire_item(item);
  ret=0;

  /* cleanup */
  mutex_unlock(...);

lock_failed:
  free(mem2);

mem2_failed:
  free(mem1);

mem1_failed:
  return ret;
}
like image 34
jpalecek Avatar answered Oct 21 '22 02:10

jpalecek


This is where a goto is appropriate, in my opinion. I used to follow the anti-goto dogma, but I changed that when it was pointed out to me that do { ... } while (0); compiles to the same code, but isn't as easy to read. Just follow the some basic rules, like not going backwards with them, keeping them to a minimum, only using them for error conditions, etc...

int func(void **mem1, void **mem2)
{
    *mem1 = NULL;
    *mem2 = NULL;

    *mem1 = malloc(SIZE);
    if(!*mem1)
        goto err;

    *mem2 = malloc(SIZE);
    if(!*mem2)
        goto err;

    return 0;
err:
    if(*mem1)
        free(*mem1);
    if(*mem2)
        free(*mem2);

    *mem1 = *mem2 = NULL;

    return 1;
}
like image 7
Ryan Graham Avatar answered Oct 21 '22 00:10

Ryan Graham