我正在尝试用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
}
处理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
}
虽然我相信它现在更简洁,更清晰,更漂亮,但我的队友仍然强烈反对使用'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
}
嗯,这似乎是一种避免使用'goto'的方法,但这种方式会增加缩进,这会使代码难看。
所以我的问题是,还有其他任何方法可以以良好的代码风格处理许多'malloc'失败吗?谢谢大家。
答案 0 :(得分:6)
goto
是合法的。我认为do{}while(0)
块没有特别的优势,因为它不太明显它所遵循的模式。
答案 1 :(得分:5)
首先,goto没有任何问题 - 这是对goto
的完全合法使用。带有do { ... } while(0)
语句的break
只是伪装成的,它只是用来模糊代码。在这种情况下,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;
}
然后用xmalloc
代替malloc
,你不再需要检查返回值,因为如果返回值,它将返回一个有效指针。但是,当然,只有当您希望分配失败是不可恢复的失败时,这才可用。如果你想能够恢复,那么你真的需要检查每个分配的结果(虽然老实说,你可能很快会有另一个失败)。
答案 2 :(得分:2)
询问你的队友如何重写这类代码:
if (!grabResource1()) goto res1failed;
if (!grabResource2()) goto res2failed;
if (!grabResource3()) goto res3failed;
(do stuff)
res3failed:
releaseResource2();
res2failed:
releaseResource1();
res1failed:
return;
并询问他如何将其推广到n
资源。 (这里,“抓取一个资源”可能意味着锁定一个互斥锁,打开一个文件,分配内存等。“free on NULL就行了”黑客并不能解决所有问题...)
这里,goto
的替代方法是创建一系列嵌套函数:抓取资源,调用抓取另一个资源的函数,并调用另一个抓取资源并调用另一个函数的函数...函数失败,其调用者可以释放其资源并返回失败,因此释放发生在堆栈展开时。但是你真的认为这比gotos更容易阅读吗?
(旁白:C ++有构造函数,析构函数和RAII成语来处理这类事情。但在C中,这是goto
显然是正确答案的一种情况,IMO。)
答案 3 :(得分:1)
错误处理中的goto没有任何问题,使用do {...} while(0)之间实际上没有代码差异;休息;而不是goto(因为它们都是jmp指令)。我会说这似乎很正常。你可以做的一件事就是创建一个int *类型的数组并在调用malloc时迭代。如果一个失败,则释放非空的并返回错误代码。这是我能想到的最干净的方式,比如
int *arr[4];
unsigned int i;
for (i = 0; i < 4; ++i)
if (!(arr[i] = malloc(sizeof(int))) {
retCode = -(i + 1); //or w/e error
break;
}
if (errorCode)
for (i = 0; i < 4; i++)
if (arr[i])
free(arr[i]);
else
break;
或沿着这些方向的东西(用过大脑编译器,所以我可能错了) 这不仅缩短了你的代码,而且避免了goto(我没有看到任何错误),所以你和你的队友都会很开心:D
答案 4 :(得分:0)
他还实现了竞技场界面。 Arena接口让您无需为每个malloc()调用free()。相反,只有一个电话可以释放整个舞台。
答案 5 :(得分:0)
如果分配失败,只需正常分配错误代码即可。像这样对每个malloc进行条件化:
if (retCode < 0) malloc...
然后在代码的末尾添加:
int * p_array[] = { p1, p2, p3, p4};
for (int x = -retCode + 1; x >= 0; x-- )
{
free(p_array[x]);
}