代码之家  ›  专栏  ›  技术社区  ›  Will Hartung

在纯C程序中有什么好的错误处理习惯用法吗?

  •  29
  • Will Hartung  · 技术社区  · 16 年前

    回到C工作。

    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 回复  |  直到 16 年前
        1
  •  17
  •   Bobby    14 年前

    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();
    }
    
        2
  •  35
  •   Judge Maygarden    16 年前

    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;
    }
    
        3
  •  8
  •   Aaron    16 年前

    你在教室里干什么 else 声明?如果没有,请尝试以下操作:

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

    这样,您就短路了整个函数,甚至不必担心下面的函数调用。

    回到过去,我又读了一遍,我意识到你在家里做什么并不重要 其他的 声明。这种代码可以很容易地在 if 阻碍。

        4
  •  6
  •   lhf    16 年前

    如果错误代码是布尔型的,请尝试下面更简单的代码:

    return func1() && func2() && func3()
    
        5
  •  5
  •   Nick Van Brunt    16 年前

    /* 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");
    }
    
        6
  •  4
  •   jade Michael Borgwardt    16 年前

    其他人提出了好主意。以下是我见过的成语

    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 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\u buf和一个setjmp函数的声明就是设置try块。setjmp返回非零的情况是catch,调用longjmp是抛出。我写这篇文章的时候传递了jmpèbuf,以防您需要嵌套处理程序(例如,如果您需要在发出错误信号之前释放内容);如果您不需要它,可以随意声明err和jmp\u buf为globals。

    #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;
    }
    

    --

    呸。我受够了(疯狂的例子未经检验。一些细节可能会被删除。)

        7
  •  3
  •   Puppy    16 年前

    你应该看看DirectX对HRESULT做了什么-基本上就是这样。例外的产生是有原因的。或者,如果你在Win32上运行,他们有SEH在C程序中运行。

        8
  •  3
  •   Jason Williams    16 年前

    而现在为了完全不同的东西。。。

    另一种方法是使用结构来包含错误信息,例如:

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

    这提供了丰富的错误报告:如果方法失败,您可以填写一个以上的简单错误代码(例如错误消息,代码行和失败的文件,或其他)。作为一个结构的好处是,如果你以后想一些有用的东西,你可以添加它——例如,在我上面的结构中,我允许调试构建包含错误的位置(文件/行),但是您可以随时在其中添加整个调用堆栈的转储,而不必更改任何客户机代码。

    您可以使用全局函数来填写ErrorInfo,以便可以干净地管理错误返回,并且可以更新结构以方便地提供更多信息:

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

    …您可以使用此函数的变体返回FALSE、0或NULL,以允许将大多数错误返回表述为一行:

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

    这为您提供了其他语言中异常类的许多优点(尽管调用者仍然需要处理错误-调用者必须检查错误代码,并且可能必须提前返回,但是它们什么也不做或者几乎什么都不做,并且允许错误传播到一系列调用方法中,直到其中一个方法希望处理它,就像异常一样。

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

    然后,如果从调用的函数中“捕获”错误,则可以创建新的更高级别的错误描述,并返回这些错误的链。e、 g.“鼠标速度将恢复为默认值”(因为)“无法找到首选项块‘MousePrefs’”(因为“XML读取器失败”(因为)“找不到文件”)。

    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"));
        ...
    }
    
        9
  •  2
  •   nategoose    16 年前

    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);
         }
    }
    

    这可能不是您真正想要做的,而是使用了多少函数式编程语言,更常见的是它们如何对代码进行优化建模。

        10
  •  1
  •   Nils Pipenbrinck    16 年前

    我最近看到的是:

    int err;
    do 
    {
      err = func1 (...);
      if (!err) break;
    
      err = func2 (...);
      if (!err) break;
    
      err = func3 (...);
      if (!err) break;
    
      /* add more calls here */
    
    } while (0);
    
    if (err)
    {
      /* handle the error here */
      return E_ERROR; /* or something else */
    }
     else 
    {
      return E_SUCCESS;
    }
    

    do/while循环构造的意义并不明显,因为循环构造被用作廉价的goto替换,这只能在循环尾部看到。我第一次肯定这个构造会引起很多“WTF”时刻。

    至少需要一个注释来解释为什么代码是按要求的方式编写的。

        11
  •  1
  •   pivz    16 年前

    这是一篇内容丰富的文章&IBM Unix测试文件系列文章:

    使用标准错误机制

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

        12
  •  0
  •   u0b34a0f6ae    14 年前

    如果您使用的是特定的上下文,我认为下面的模式非常好。其基本思想是对错误集状态的操作是不可操作的,因此可以将错误检查推迟到方便的时候!

    一个具体的例子:反序列化上下文。任何元素的解码都可能失败,但该功能可能会在不进行错误检查的情况下继续,因为所有 decode_* decode_has_error . 在下面的例子中,没有错误检查,调用者会处理这个问题。

    void list_decode(struct serialization_record *rec,                       
                     struct list *list,                                     
                     void *(*child_decode)(struct serialization_record *)) {
        uint32_t length;                                                             
        decode_begin(rec, TAG);                                  
        decode_uint32(rec, &length);                                          
        for (uint32_t i = 0; i < length; i++) {                                
            list_append(list, child_decode(rec));
        }                                                                        
        decode_end(rec, TAG);
    }