有没有其他方法来处理许多'malloc'失败?

时间:2011-07-20 01:09:58

标签: c malloc

我正在尝试用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'失败吗?谢谢大家。

6 个答案:

答案 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)

David Hanson撰写了“ C接口和实现:创建可重用软件的技术”一书。他的Mem接口提供的功能“与标准C库中的类似,但它们不接受零大小,永远不会返回空指针”。源代码包括生产实现和检查实现。

他还实现了竞技场界面。 Arena接口让您无需为每个malloc()调用free()。相反,只有一个电话可以释放整个舞台。

CII source code

答案 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]);
}