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

Sta*_*tan 5 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
}
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"失败?谢谢你们.

Yan*_*min 6

goto在这种情况下是合法的.我认为该do{}while(0)块没有特别的优势,因为它不太明显它遵循的模式.


Ada*_*eld 5

首先,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,你不再需要检查返回值,因为如果它返回它将返回一个有效的指针.但是,当然,只有当您希望分配失败是不可恢复的失败时,这才可用.如果你想恢复,那么你真的需要检查每个分配的结果(虽然老实说,你可能很快就会有另一个失败).


Nem*_*emo 2

询问你的队友他将如何重写此类代码:

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。)