Обработка ошибок короткого замыкания в C - PullRequest
3 голосов
/ 13 апреля 2009

Мне было интересно, есть ли лучший способ обработки случая в C, где вы хотите выйти из функции, как только вы столкнетесь с ошибкой в ​​серии выражений. (в данном случае это функция, которая возвращает NULL при ошибке)

например. в некотором C-коде, где они пытались замкнуть обработку ошибок, комбинируя серию операторов с AND (&&).

return doSomething() && 
     doSomething2() && 
     doSomething3() && ... ;

Это раздражает меня, так как мы втискиваем столько в одну строку в одном утверждении. Но я думаю, что альтернатива

if (!(value = doSomething()))
    return NULL;
if (!(value = doSomething2()))
    return NULL;
etc
return value;

Но как насчет оценки ошибок короткого замыкания, которые я видел в сценариях perl и bash?

int die(int retvalue) {
    exit(retvalue);
}
.....
(value = doSomething()) || die(1);
(value = doSomething2()) || die(2);
(value = doSomething3()) || die(3);
return value;

Основная проблема в том, что RHS должен быть выражением, чтобы вы не могли по-настоящему перейти к функции или вернуться к ней. Кто-нибудь найдет это ценным или слишком ограниченным?

edit: Я думаю, мне следовало включить в первый пример символы новой строки. Проблема в том, что вам нужно быть осторожным, если вы решите добавить другое выражение в середине.

Ответы [ 6 ]

5 голосов
/ 13 апреля 2009

Я видел использование GOTO в Си для этой цели.

Поскольку в C нет конструкции finally, был необходим метод для освобождения всей вашей памяти, даже если вы выходили из функции раньше.

Так что по сути это так (может кто-то исправить мой синтаксис C, если я ошибаюсь, я немного заржавел)

int foo()
{
    /* Do Stuff */
    if(!DoSomething())
        GOTO Failure;

    if(!DoSomething2())
        GOTO Failure;

    if(!DoSomething3())
        GOTO Failure;

    /* return success */
    return true; 

    Failure:
    /* release allocated resources */
    /* print to log if necessary */
    return false;
}

Важное примечание Не используйте GOTO для выполнения потока. Они должны использоваться только при ошибке и только для перехода к концу текущей функции. Если вы используете их для чего-то еще, вы создаете код спагетти, который может разрушить ткань реальности. Только не делай этого.

EDIT

Как отметил один из авторов, использование Exit (x) убьет всю вашу программу, что оставляет это решение зарезервированным для фатальных ошибок. Однако ваше оригинальное предлагаемое решение (DS () && DS2 () && DS3 ()) в одной строке создает проблему для обработки ошибок.

Если вы хотите обернуть функции в какую-то функцию обработки ошибок, специфичную для функции, нет способа сделать это, когда вы оборачиваете вызовы всех функций в одну строку. Итак, по крайней мере, вы могли бы сделать что-то вроде

int result1 = 0;
int result2 = 0;
int result3 = 0;

result1 = DoSomething();

if(result1)
    result2 = DoSomething2();

if(result2)
    result3 = DoSomething3();

return result1 && result2 && result3;

Поскольку этот метод не исключает обработку ошибок.

4 голосов
/ 13 апреля 2009

Если ваше единственное беспокойство - слишком много в одну строку, почему бы вам просто не использовать:

return doSomething()
    && doSomething2()
    && doSomething3()
    && ... ;

Я обычно пишу второй случай как:

if (!(value = doSomething()))  return NULL;
if (!(value = doSomething2())) return NULL;
: : : : :
return value;

(даже выравнивание возвратов, чтобы сделать его читабельным), так как мне нравится видеть столько кода на экране, сколько раз (и вы можете вставлять другие проверки между строками по мере необходимости).

1 голос
/ 13 апреля 2009

Как насчет этого?

int result;

result = doSomething();

result = result && doSomething2();

result = result && doSomething3();

return result;

При этом используется короткое замыкание аналогично первому примеру, но оно позволяет разбить его на несколько строк, добавить комментарии и т. Д.

0 голосов
/ 13 апреля 2009

Я видел это раньше:

int Function (void)
{
    int Result = DoSomething();

    if (Result) Result = DoSomething2();
    if (Result) Result = DoSomething3();
    if (Result) Result = DoSeomthing4();

    return Result;
}

Это выглядит достаточно аккуратно, легко выравнивается. Он не выйдет сразу, но больше ничего не выполнит. Это, конечно, предполагает, что функции возвращают ненулевое значение в случае успеха. В противном случае вы можете просто использовать if (!Result), если функции возвращают ненулевое значение при сбое.

0 голосов
/ 13 апреля 2009

Идиоматический способ написания этого на C, по моему опыту, состоит в том, чтобы запустить функцию с помощью серии операторов if, которые проверяют предварительные условия для остальной части функции. Если предварительное условие не выполнено, выполняется немедленный возврат с кодом ошибки. Таким образом, когда вы добираетесь до основной части тела функции, вы знаете, что все в порядке и можете сохранить код максимально простым.

Другими словами, вы второй подход.

Например, можно написать функцию для копирования строки следующим образом:

int copy_string(char *source, char *target, size_t max)
{
    if (source == NULL)
        return -1;
    if (target == NULL)
        return -1;
    source_len = strlen(source);
    if (source_len + 1 > max)   // +1 for NUL
        return -1;
    memcpy(target, source, source_len + 1);
    return 0;
}

(мне нравится соглашение системных вызовов Unix, возвращающее -1 для ошибок, но это спорный вопрос стиля и не имеет отношения к этому вопросу.)

0 голосов
/ 13 апреля 2009

Я рекомендую против предложенной вами техники. Сначала выход в C завершает процесс; это не просто возвращается. Таким образом, он ограничен только несколькими случаями фатальных ошибок.

Первое решение, которого вы пытаетесь избежать, является самым лучшим и легким для понимания с моей точки зрения.

...