12

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

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

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

    return 0;
}

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

4

9 回答 9

26

我知道人们不愿意使用它们,但这是gotoC语言中的完美情况。

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;
}      
于 2009-02-20T17:05:56.080 回答
7

在我看来,这就是 goto 合适的地方。我曾经遵循反 goto 教条,但当有人向我指出 do { ... } 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;
}
于 2009-02-20T17:08:46.230 回答
5

这有点争议,但我认为gotoLinux 内核中使用的方法实际上在这种情况下效果很好:

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;
}
于 2009-02-20T17:12:23.927 回答
2

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

int func(void **mem1, void **mem2) {
  *mem1 = malloc(SIZE);
  *mem2 = malloc(SIZE);
  if (!*mem1 || !*mem2) {
    free(*mem2);
    free(*mem1);
    return 1;
  }
  return 0;
}
于 2009-05-17T06:33:25.873 回答
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 );

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

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

res_delete_set( resource_set );

我不需要专门检查错误——我的代码中没有if()s 检查返回值,这使得它可以维护;我发现内联错误检查的泛滥破坏了可读性和可维护性。我只有一个很好的简单的函数调用列表。

这是艺术,伙计:-)

于 2009-05-17T06:38:13.710 回答
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()这里使用你的函数进行清理。

于 2009-02-20T16:59:50.980 回答
0

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

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

if (func(&mem1, &mem2)) {
    freeAll(2, mem1, mem2);
    return 1;
}
于 2009-02-20T16:53:55.017 回答
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;
}

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

于 2009-05-17T06:12:32.460 回答
-2

我对 goto 语句的所有建议感到有点害怕!

我发现使用 goto 会导致代码混乱,这更有可能导致程序员错误。我现在的偏好是完全避免使用它,除非在最极端的情况下。我几乎从不使用它。不是因为学术完美主义,而是因为一年或更长时间后,回忆整体逻辑似乎总是比我建议的替代方案更难。

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

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

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

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

于 2009-05-17T04:31:57.617 回答