What is the best way to free memory after returnin

2019-01-22 09:31发布

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.

9条回答
小情绪 Triste *
2楼-- · 2019-01-22 09:54

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;
}      
查看更多
唯我独甜
3楼-- · 2019-01-22 09:58

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;
}
查看更多
放我归山
4楼-- · 2019-01-22 09:58

Personally; I have a resource tracking library (basically a balanced binary tree) and I have wrappers for all allocation functions.

Resources (such as memory, sockets, file descriptors, semaphores, etc - anything you allocate and deallocate) can belong to a set.

I also have an error handling library, whereby the first argument to each function is an error set and if something goes wrong, the function experiencing an error submits an error into the error set.

If an error set contains an error, no functions execute. (I have a macro at the top of every function which causes it to return).

So multiple mallocs look like this;

mem[0] = malloc_wrapper( error_set, resource_set, 100 );
mem[1] = malloc_wrapper( error_set, resource_set, 50 );
mem[2] = malloc_wrapper( error_set, resource_set, 20 );

There is no need to check the return value, because if an error occurs, no following functions will execute, e.g. the following mallocs never occur.

So, when the time comes for me to deallocate resources (say at the end of a function, where all the resources used internally by that function have been placed into a set), I deallocate the set. It's just one function call.

res_delete_set( resource_set );

I don't need to specifically check for errors - there are no if()s in my code checking return values, which makes it maintainable; I find the profliferation of in-line error check destroys readability and so maintainability. I just have a nice plain list of function calls.

It's art, man :-)

查看更多
可以哭但决不认输i
5楼-- · 2019-01-22 09:59

If the above goto statements horrify you for some reason, you can always do something like this:

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

    *mem2 = malloc(SIZE);
    if (!*mem2) {
        /* Insert free statement here */
        free(*mem1);
        return 1;
    }

    return 0;
}

I use this method pretty regularly, but only when it's very clear what's going on.

查看更多
霸刀☆藐视天下
6楼-- · 2019-01-22 10:02

My own inclination is to create a variable argument function that frees all non-NULL pointers. Then the caller can handle the error case:

void *mem1 = NULL;
void *mem2 = NULL;

if (func(&mem1, &mem2)) {
    freeAll(2, mem1, mem2);
    return 1;
}
查看更多
不美不萌又怎样
7楼-- · 2019-01-22 10:10

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;
}
查看更多
登录 后发表回答