假设我有一个为调用者分配内存的函数:
int func(void **mem1, void **mem2) {
*mem1 = malloc(SIZE);
if (!*mem1) return 1;
*mem2 = malloc(SIZE);
if (!*mem2) {
/* ... */
return 1;
}
return 0;
}
如果第二个malloc()失败,我想听听你对释放()分配内存的最佳方法的反馈意见。你可以想象一个更复杂的情况,有更多的错误退出点和更多分配的内存。
答案 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;
}
我最后只想从一个函数返回一个。跳出来之间很快(在我看来,有点脏)。但更重要的是,允许您无意中轻松绕过相关的清理代码。