直接C程序中任何错误处理的好习惯用法?

时间:2010-05-07 16:11:40

标签: c error-handling

重新参加一些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 ++都有例外,可以很好地处理这些事情。

我只是好奇其他人做了什么以及其他人如何在他们的C程序中进行错误处理。

12 个答案:

答案 0 :(得分:32)

如果你有资源需要在最后发布,那么有时旧的可靠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;
}

答案 1 :(得分:15)

两种典型模式:

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 :(得分:8)

你在else陈述中做了什么?如果没有,试试这个:

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

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

err = func3(...);

return err;

这样你就可以使整个功能短路,甚至不会打扰以下函数调用。

修改

回头再读一遍,我意识到你在else陈述中做了什么并不重要。在if块之后,这种代码可以很容易地立即执行。

答案 3 :(得分:6)

如果错误代码是布尔值,那么请尝试下面的简单代码:

return func1() && func2() && func3()

答案 4 :(得分:4)

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

答案 5 :(得分:4)

其他人提出了好主意。这是我见过的习语

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返回非零的情况是你的catch,调用longjmp就是你的throw。我写了这个,传递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;
}

-

呼。我受够了。 (疯狂的例子未经测试。有些细节可能会关闭。)

答案 6 :(得分:3)

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

答案 7 :(得分:2)

你可以变得非常愚蠢并继续下去:

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

这实际上可能不是你想要做的,但它是使用了多少函数式编程语言,更常见的是他们如何为优化建模代码。

答案 8 :(得分:2)

现在有一些完全不同的东西......

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

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

使用它的最佳方法是将方法的结果作为返回码返回(例如“FALSE表示失败”,“文件指针或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类的许多优点(尽管调用者仍然需要处理错误 - 调用者必须检查错误代码并且可能必须提前返回,但他们无法做任何事情或者下一步-to-nothing并允许错误传播回一系列调用方法,直到其中一个希望处理它,就像异常一样。

此外,您可以更进一步,创建一系列错误报告(如“InnerException”):

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

然后,如果您从您调用的函数中“捕获”错误,则可以创建新的更高级别的错误描述,并返回这些错误的链。例如“鼠标速度将恢复为默认值”(因为)“无法找到”首选项块'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 :(得分:1)

我最近看到的是这个偶像:

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

专业论据:

它避免了goto(滥用while(0)/ break组合)。你为什么想做这个?它会降低圈复杂度并仍然会通过大多数静态代码分析器检查(MISRA任何人?)。对于针对圈复杂度进行测试的项目,这是一个上帝发送,因为它将所有初始化的东西保持在一起。

反对论点:

do / while循环结构的含义并不明显,因为循环结构被用作廉价的goto替换,这只能在循环尾部看到。我确信这个结构第一次会引起很多“WTF” - 动作。

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

答案 10 :(得分:1)

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

错误:UNIX程序中的错误

使用标准错误机制

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

如何实现退出代码的另一个很好的例子是curl的源代码(man 1 curl)。

答案 11 :(得分:0)

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

一个具体示例:反序列化上下文。任何元素的解码都可能失败,但该函数可以继续而不进行错误检查,因为当序列化记录处于错误状态时,所有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);
}