从错误返回后释放内存的最佳方法是什么?

时间:2009-02-20 16:46:55

标签: c memory free malloc

假设我有一个为调用者分配内存的函数:

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

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

    return 0;
}

如果第二个malloc()失败,我想听听你对释放()分配内存的最佳方法的反馈意见。你可以想象一个更复杂的情况,有更多的错误退出点和更多分配的内存。

9 个答案:

答案 0 :(得分:25)

我知道人们不喜欢使用它们,但这是C中goto的完美情况。

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;
}      

答案 1 :(得分:7)

在我看来,这是goto合适的地方。我过去常常遵循反goto教条,但当我向我指出{...} while(0)时,我改变了这一点。编译成相同的代码,但不容易阅读。只需遵循一些基本规则,例如不要与它们一起倒退,将它们保持在最低限度,仅将它们用于错误条件等等......

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;
}

答案 2 :(得分:4)

这有点争议,但我认为Linux内核中使用的goto方法在这种情况下实际上运行良好:

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;
}

答案 3 :(得分:2)

这是一个可读的替代方案:

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

答案 4 :(得分:2)

个人;我有一个资源跟踪库(基本上是一个平衡的二叉树),我有所有分配函数的包装器。

资源(例如内存,套接字,文件描述符,信号量等 - 您分配和解除分配的任何内容)都属于一个集合。

我还有一个错误处理库,每个函数的第一个参数是一个错误集,如果出现错误,遇到错误的函数会向错误集提交错误。

如果错误集包含错误,则无函数执行。 (我在每个函数的顶部都有一个宏,导致它返回。)

所以多个malloc看起来像这样;

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 );

无需检查返回值,因为如果发生错误,将不会执行以下函数,例如以下mallocs永远不会发生。

因此,当我需要释放资源时(比如在函数结束时,该函数内部使用的所有资源都已放入集合中),我会释放该集合。这只是一个函数调用。

res_delete_set( resource_set );

我不需要专门检查错误 - 我的代码中有 if()s检查返回值,这使得它可维护;我发现在线错误检查的前瞻性破坏了可读性和可维护性。我只是有一个很好的函数调用列表。

这是艺术,男人: - )

答案 5 :(得分:1)

调用者是否对失败前已正确分配的内存块执行了任何有用的操作?如果没有,被调用者应该处理释放。

有效清理的一种可能性是使用do..while(0),允许break您的示例return

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

    do
    {
        *mem1 = malloc(SIZE);
        if(!*mem1) break;

        *mem2 = malloc(SIZE);
        if(!*mem2) break;

        return 0;
    } while(0);

    // free is NULL-safe
    free(*mem1);
    free(*mem2);

    return 1;
}

如果您进行了大量分配,您可能还想使用freeAll()函数在此处进行清理。

答案 6 :(得分:0)

我自己的倾向是创建一个可变参数函数来释放所有非NULL指针。然后调用者可以处理错误情况:

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

if (func(&mem1, &mem2)) {
    freeAll(2, mem1, mem2);
    return 1;
}

答案 7 :(得分:0)

如果上述goto语句出于某种原因让你感到恐惧,你总是可以这样做:

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;
}

我经常使用这种方法,但只有当它非常清楚发生了什么时才会发生。

答案 8 :(得分:-2)

我对goto声明的所有建议感到有点害怕!

我发现使用goto会导致代码混乱,这更容易引起程序员错误。我现在的偏好是完全避免使用它,除非在最极端的情况下。我几乎从不使用它。不是因为学术上的完美主义,而是因为一年或更长时间后,总体上看起来似乎更难以回想起我所建议的替代方案。

作为一个喜欢重构事物以最大限度地减少忘记内容的选择(比如清除指针)的人,我先添加一些函数。我认为我可能会在同一个程序中重复使用这些。函数imalloc()将使用间接指针执行malloc操作; ifree()会撤消这个。 cifree()会有条件地释放内存。

有了这个,我的代码版本(带有第三个arg,为了演示)将是这样的:

// indirect pointer malloc
int imalloc(void **mem, size_t size)
{
   return (*mem = malloc(size));
}

// indirect pointer free
void ifree(void **mem)
{
   if(*mem)
   {
     free(*mem);
     *mem = NULL;
   }
}

// conditional indirect pointer free
void cifree(int cond, void **mem)
{
  if(!cond)
  {
    ifree(mem);
  }
}

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

   if(imalloc(mem1, SIZE))
   {
     if(imalloc(mem2, SIZE))
     {
       if(imalloc(mem3, SIZE))
       {
         result = TRUE;
       }            
       cifree(result, mem2);
     }
     cifree(result, mem1);
   }
  return result;
}

我最后只想从一个函数返回一个。跳出来之间很快(在我看来,有点脏)。但更重要的是,允许您无意中轻松绕过相关的清理代码。