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

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

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

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

我知道人们不喜欢使用它们,但这是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; } 

在我看来,这是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; } 

这有点争议,但我认为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; } 

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

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

调用者是否对在失败之前正确分配的内存块做了有用的事情? 如果不是,被调用者应该处理释放。

有效清理的一种可能性是使用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()函数来进行清理。

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

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

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

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

所以多个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检查返回值,这使得它可以维护; 我发现在线错误检查的前瞻性破坏了可读性和可维护性。 我只是有一个很好的函数调用列表。

这是艺术 ,男人:-)

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

 void *mem1 = NULL; void *mem2 = NULL; if (func(&mem1, &mem2)) { freeAll(2, mem1, mem2); return 1; } 

如果上面的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; } 

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

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

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