Любые хорошие идиомы для обработки ошибок в прямых программах на C? - PullRequest
29 голосов
/ 07 мая 2010

Возвращаясь к работе на Си.

Многие из моих функций выглядят так:

int err = do_something(arg1, arg2, arg3, &result);

С намерением результат заполняется функцией, а возвращаемое значение является состоянием вызова.

Темная сторона - это что-то наивное, похожее на это:

int err = func1(...);
if (!err) {
    err = func2(...);
    if (!err) {
        err = func3(...);
    }
}
return err;

Я мог бы макрос это, я полагаю:

#define ERR(x) if (!err) { err = (x) }
int err = 0;
ERR(func1(...));
ERR(func2(...));
ERR(func3(...));
return err;

Но это работает, только если я соединяю вызовы функций, а не выполняю другую работу.

Очевидно, в Java, C #, C ++ есть исключения, которые очень хорошо работают для такого рода вещей.

Мне просто любопытно, что другие люди делают и как другие люди в настоящее время обрабатывают ошибки в своих программах на Си.

Ответы [ 12 ]

32 голосов
/ 07 мая 2010

Если у вас есть ресурсы, которые должны быть освобождены в конце, то иногда может пригодиться старый надежный goto!

int
major_func(size_t len)
{
    int err;
    char *buf;

    buf = malloc(len);

    if (err = minor_func1(buf))
        goto major_func_end;
    if (err = minor_func2(buf))
        goto major_func_end;
    if (err = minor_func3(buf))
        goto major_func_end;

major_func_end:
    free(buf);
    return err;
}
16 голосов
/ 07 мая 2010

Два типичных шаблона:

int major_func()
{
    int err = 0;

    if (err = minor_func1()) return err;
    if (err = minor_func2()) return err;
    if (err = minor_func3()) return err;

    return 0;
}

int other_idea()
{
    int err = minor_func1();
    if (!err)
        err = minor_func2();
    if (!err)
        err = minor_func3();
    return err;            
}

void main_func()
{
    int err = major_func();
    if (err)
    {
        show_err();
        return;
    }
    happy_happy_joy_joy();

    err = other_idea();
    if (err)
    {
        show_err();
        return;
    }
    happy_happy_joy_joy();
}
8 голосов
/ 07 мая 2010

Что вы делаете в else заявлениях? Если ничего, попробуйте это:

int err = func1(...);
if (err) {
    return err;
}

err = func2(...);
if (err) {
    return err;
}

err = func3(...);

return err;

Таким образом, вы закорачиваете всю функцию, даже не заботясь о следующих вызовах функций.

EDIT

Возвращаясь и читая снова, я понимаю, что не имеет значения, что вы делаете в ваших else заявлениях. Этот вид кода может легко идти сразу после блоков if.

6 голосов
/ 07 мая 2010

Если коды ошибок логические, попробуйте более простой код ниже:

return func1() && func2() && func3()
4 голосов
/ 07 мая 2010

Другие предложили хорошие идеи. Вот идиомы, которые я видел

int err;
...
err = foo(...);
if (err)
    return err;
...

Вы можете макрокомандировать это что-то вроде

#define dERR int err=0
#define CALL err = 
#define CHECK do { if (err) return err } while(0)
...
void my_func(void) {
   dERR;
   ...
   CALL foo(...);
   CHECK;

или, если вы действительно заинтересованы, возьмите CALL и CHECK, чтобы их можно было использовать как

CALL foo(...) CHECK;

или

CALL( foo(...) );

-

Часто функции, которые должны выполнять очистку при выходе (например, свободную память), пишутся так:

int do_something_complicated(...) {
    ...

    err = first_thing();
    if (err)
       goto err_out;

    buffer = malloc(...);
    if (buffer == NULL)
        goto err_out

    err = another_complicated(...);
    if (err)
        goto err_out_free;

    ...

   err_out_free:
    free(buffer);
   err_out:
    return err; /* err might be zero */
}

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

-

Наконец, если вы чувствуете / действительно / мотивированы, вы можете использовать setjmp / longjmp.

int main(int argc, char *argv[]) {
    jmp_buf on_error;
    int err;
    if (err = setjmp(on_error)) {
        /* error occurred, error code in err */
        return 1;
    } else {
        actual_code(..., on_error);
        return 0;
    }
}
void actual_code(..., jmp_buf on_error) {
    ...
    if (err)
        longjmp(on_error, err);
}

По сути, объявление нового jmp_buf и функции setjmp как установка блока try. Случай, когда setjmp возвращает ненулевое значение, является вашим уловом, а вызов longjmp - вашим броском. Я написал это с передачей jmp_buf на случай, если вам нужны вложенные обработчики (например, если вам нужно освободить вещи перед тем, как сообщить об ошибке); если вам это не нужно, не стесняйтесь объявлять err и jmp_buf глобальными.

С другой стороны, вы можете использовать макросы, чтобы просто передать аргумент. Я бы предложил способ реализации Perl:

#define pERR jmp_buf _err_handler
#define aERR _err_handler
#define HANDLE_ERRORS do { jmp_buf _err_handler; int err = setjmp(_err_handler);
#define END_HANDLE while(0)
#define TRY if (! err)
#define CATCH else
#define THROW(e) longjmp(_err_handler, e)

void always_fails(pERR, int other_arg) {
    THROW(42);
}
void does_some_stuff(pERR) {
    normal_call(aERR);
    HANDLE_ERRORS
      TRY {
        always_fails(aERR, 23);
      } CATCH {
        /* err is 42 */
      }
    END_HANDLE;
}
int main(int argc, char *argv[]) {
    HANDLE_ERRORS
      TRY {
        does_some_stuff(aERR);
        return 0;
      } CATCH {
        return err;
      }
    DONE_ERRORS;
}

-

Уф. Я задолбался. (Сумасшедшие примеры не проверены. Некоторые детали могут быть отключены.)

4 голосов
/ 07 мая 2010

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

/* call a number of functions which may error.. */
glMatrixMode(GL_MODELVIEW);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnable(GL_TEXTURE_2D);

/* ...check for errors */
if ((error = glGetError()) != GL_NO_ERROR) {
    if (error == GL_INVALID_VALUE)
        printf("error: invalid value creating view");
    else if (error == GL_INVALID_OPERATION)
        printf("error: invalid operation creating view");
    else if (error == GL_OUT_OF_MEMORY)
        printf("error: out of memory creating view");
}
3 голосов
/ 07 мая 2010

Вы должны проверить, что DirectX сделал с HRESULT - это в основном это. Есть причина, по которой возникло исключение. В качестве альтернативы, если вы работаете на Win32, у них есть SEH, который работает в программах на Си.

2 голосов
/ 08 мая 2010

А теперь что-то совершенно другое ...

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

struct ErrorInfo
{
    int errorCode;
    char *errorMessage;
#if DEBUG
    char *functionName;
    int lineNumber;
#endif
}

Лучший способ использовать это - вернуть результаты вашего метода в качестве кода возврата (например, «FALSE for fail», или «указатель файла или NULL в случае сбоя», «размер буфера или 0 в случае сбоя»). "и т. д.) и передайте ErrorInfo в качестве параметра, который будет вызывать вызываемая функция в случае сбоя.

Это дает подробные отчеты об ошибках: если метод завершается ошибкой, вы можете ввести более простой код ошибки (например, сообщение об ошибке, строку кода и файл ошибки или что-то еще). Хорошая вещь в том, что структура - это то, что если вы думаете о чем-то, что-нибудь полезное позже, вы можете просто добавить это - например, в моей структуре выше я позволил отладочной сборке включить местоположение ошибки ( файл / строка), но вы можете в любой момент добавить туда дамп всего стека вызовов, не изменяя код клиента.

Вы можете использовать глобальную функцию для заполнения ErrorInfo, чтобы можно было аккуратно управлять возвратом ошибки, и вы можете обновить структуру, чтобы легко предоставлять больше информации:

if (error)
{
    Error(pErrorInfo, 123, "It failed");
    return(FALSE);
}

... и у вас могут быть варианты этой функции, которые возвращают FALSE, 0 или NULL, чтобы позволить большинству возвратов ошибок быть выраженными в одной строке:

if (error)
    return(ErrorNull(pErrorInfo, 123, "It failed"));

Это дает вам множество преимуществ класса Exception на других языках (хотя вызывающему все еще нужно обрабатывать ошибки - вызывающие должны проверять коды ошибок и, возможно, должны вернуться рано, но они ничего не могут сделать или затем - в ничто и позволяют ошибке распространяться по цепочке вызывающих методов до тех пор, пока один из них не захочет ее обработать, как исключение.

Кроме того, вы можете пойти дальше, чтобы создать цепочку отчетов об ошибках (например, «InnerException»):

struct ErrorInfo
{
    int errorCode;
    char *errorMessage;
    ...
    ErrorInfo *pInnerError;    // Pointer to previous error that may have led to this one
}

Затем, если вы «поймаете» ошибку из вызываемой функции, вы можете создать новое описание ошибки более высокого уровня и вернуть цепочку этих ошибок. например «Скорость мыши вернется к значению по умолчанию» (потому что) «Блок настроек« MousePrefs »не может быть найден» (потому что) «Ошибка чтения XML» (потому что) «Файл не найден».

* * 1 022 * т.е. 1023 *
FILE *OpenFile(char *filename, ErrorInfo *pErrorInfo)
{
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL)
        return(ChainedErrorNull(pErrorInfo, "Couldn't open file"));

    return(fp);
}

XmlElement *ReadPreferenceXml(ErrorInfo *pErrorInfo)
{
    if (OpenFile("prefs.xml", pErrorInfo) == NULL)
        return(ChainedErrorNull(pErrorInfo, "Couldn't read pref"));
    ...
}

char *ReadPreference(char *prefName, ErrorInfo *pErrorInfo)
{
    XmlElement *pXml = ReadPreferenceXml(pErrorInfo);
    if (pXml == NULL)
        return(ChainedErrorNull(pErrorInfo, "Couldn't read pref"));
    ...
}
2 голосов
/ 07 мая 2010

Вы можете стать действительно глупым и продолжать:

void step_1(int a, int b, int c, void (*step_2)(int), void (*err)(void *) ) {
     if (!c) {
         err("c was 0");
     } else {
         int r = a + b/c;
         step_2(r);
     }
}

Это, вероятно, на самом деле не то, что вы хотите делать, а то, сколько функциональных языков программирования используется, и даже чаще, каксмоделируйте их код для оптимизации.

1 голос
/ 08 мая 2010

Вот довольно информативная статья и тестовый файл из серии статей IBM Unix:

Ошибки: ошибка в программах UNIX

Работа со стандартным механизмом ошибок

https://www.ibm.com/developerworks/aix/library/au-errnovariable/

Другим хорошим примером реализации кодов выхода является исходный код curl (man 1 curl).

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