Есть ли другой метод для обработки многих сбоев 'malloc'? - PullRequest
5 голосов
/ 20 июля 2011

Я пытаюсь написать функцию на 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 ]

6 голосов
/ 20 июля 2011

goto в этом случае является законным. Я не вижу особого преимущества для блока do{}while(0), поскольку он менее очевиден, по какому шаблону он следует.

5 голосов
/ 20 июля 2011

Прежде всего, в 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 голосов
/ 20 июля 2011

Спросите своего товарища по команде, как он переписал бы такой код:

if (!grabResource1()) goto res1failed;
if (!grabResource2()) goto res2failed;
if (!grabResource3()) goto res3failed;

(do stuff)

res3failed:
releaseResource2();
res2failed:
releaseResource1();
res1failed:
return;

И спросите, как он обобщил бы его на n ресурсы.(Здесь «захват ресурса» может означать блокировку мьютекса, открытие файла, выделение памяти и т. Д. Взлом «free on NULL is OK» не решает все проблемы ...)

Здесь альтернативаgoto - создать цепочку вложенных функций: захватить ресурс, вызвать функцию, которая захватывает другой ресурс и вызывает другую функцию, которая захватывает ресурс и вызывает другую функцию ... Когда функция завершается ошибкой, ее вызывающая сторона может освободить свой ресурси возврат неудачи, поэтому освобождение происходит при разматывании стека.Но действительно ли вы думаете, что это легче читать, чем gotos?

(Кроме того: C ++ имеет конструкторы, деструкторы и идиому RAII для обработки такого рода вещей. Но в C это единственный случай, когдаgoto явно правильный ответ, ИМО.)

1 голос
/ 20 июля 2011

В обработке ошибок нет ничего плохого в 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

0 голосов
/ 20 июля 2011

В случае неудачного размещения просто назначьте код ошибки как обычно. обусловить каждый malloc следующим образом:

if (retCode < 0) malloc...

и затем в конце вашего кода добавьте:

int * p_array[] = { p1, p2, p3, p4};
for (int x = -retCode + 1; x >= 0; x-- )
{ 
    free(p_array[x]);
}
0 голосов
/ 20 июля 2011

Дэвид Хэнсон написал книгу Интерфейсы и реализации C: методы создания программного обеспечения многократного использования . Его интерфейс Mem предоставляет функции, которые «похожи на функции в стандартной библиотеке C, но они не принимают нулевые размеры и никогда не возвращают нулевые указатели». Исходный код включает в себя производственную реализацию и реализацию проверки.

Он также реализует интерфейс Arena. Интерфейс Arena освобождает вас от необходимости вызывать free () для каждого malloc (). Вместо этого есть только один звонок, чтобы освободить всю арену.

Исходный код CII

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...