我正在尝试用C编写一个函数来解决数学问题.在该函数中,有几个步骤,每个步骤需要根据前面步骤中的计算结果分配一些大小的内存(所以我不能在函数的开头全部分配它们).伪代码看起来像:
int func(){
int *p1, *p2, *p3, *p4;
...
p1 = malloc(...);
if(!p1){
return -1; //fail in step 1
}
...
p2 = malloc(...);
if(!p2){
free(p1);
return -2; //fail in step 2
}
...
p3 = malloc(...);
if(!p3){
free(p1);
free(p2);
return -3; //fail in step 3
}
...
p4 = malloc(...);
if(!p4){
free(p1);
free(p2);
free(p3); /* I have to write too many "free"s here! */
return -4; //fail in step 4
}
...
free(p1);
free(p2);
free(p3);
free(p4);
return 0; //normal exit
}
Run Code Online (Sandbox Code Playgroud)
上面处理malloc失败的方法是如此丑陋.因此,我按以下方式进行:
int func(){
int *p1=NULL, *p2=NULL, *p3=NULL, *p4=NULL;
int retCode=0;
...
/* other "malloc"s and "if" blocks here */
...
p3 = malloc(...);
if(!p3){
retCode = -3; //fail in step 3
goto FREE_ALL_EXIT;
}
...
p4 = malloc(...);
if(!p4){
retCode = -4; //fail in step 4
goto FREE_ALL_EXIT;
}
...
FREE_ALL_EXIT:
free(p1);
free(p2);
free(p3);
free(p4);
return retCode; //normal exit
}
Run Code Online (Sandbox Code Playgroud)
虽然我认为它现在更简洁,更清晰,更漂亮,但我的队友仍然强烈反对使用'goto'.他建议采用以下方法:
int func(){
int *p1=NULL, *p2=NULL, *p3=NULL, *p4=NULL;
int retCode=0;
...
do{
/* other "malloc"s and "if" blocks here */
p4 = malloc(...);
if(!p4){
retCode = -4; //fail in step 4
break;
}
...
}while(0);
free(p1);
free(p2);
free(p3);
free(p4);
return retCode; //normal exit
}
Run Code Online (Sandbox Code Playgroud)
嗯,这似乎是一种避免使用'goto'的方法,但这种方式增加了缩进,这使代码变得丑陋.
所以我的问题是,是否还有其他方法可以以良好的代码风格处理许多"malloc"失败?谢谢你们.
首先,goto没有任何问题 - 这是完全合法的使用goto.在do { ... } while(0)与break语句只是GOTO语句进行了伪装,它只会混淆代码.在这种情况下,Gotos真的是最好的解决方案.
另一个选择是放置一个包装器malloc(例如调用它xmalloc),如果malloc失败则会终止程序.例如:
void *xmalloc(size_t size)
{
void *mem = malloc(size);
if(mem == NULL)
{
fprintf(stderr, "Out of memory trying to malloc %zu bytes!\n", size);
abort();
}
return mem;
}
Run Code Online (Sandbox Code Playgroud)
然后xmalloc在任何地方使用代替malloc,你不再需要检查返回值,因为如果它返回它将返回一个有效的指针.但是,当然,只有当您希望分配失败是不可恢复的失败时,这才可用.如果你想恢复,那么你真的需要检查每个分配的结果(虽然老实说,你可能很快就会有另一个失败).
询问你的队友他将如何重写此类代码:
if (!grabResource1()) goto res1failed;
if (!grabResource2()) goto res2failed;
if (!grabResource3()) goto res3failed;
(do stuff)
res3failed:
releaseResource2();
res2failed:
releaseResource1();
res1failed:
return;
Run Code Online (Sandbox Code Playgroud)
并询问他如何将其推广到n资源。(在这里,“获取资源”可能意味着锁定互斥体、打开文件、分配内存等。“NULL 上的释放是可以的”黑客并不能解决所有问题......)
在这里,替代方法goto是创建一个嵌套函数链:获取一个资源,调用一个获取另一个资源的函数,然后调用另一个获取一个资源并调用另一个函数的函数......当一个函数失败时,它的调用者可以释放它的资源。资源并返回失败,因此释放发生在堆栈展开时。但你真的认为这比 goto 更容易阅读吗?
goto(旁白:C++ 有构造函数、析构函数和 RAII 习惯用法来处理这类事情。但在 C 中,这显然是正确答案的一种情况,IMO。)