优雅的错误检查

时间:2011-08-03 19:51:08

标签: c error-handling

我们的代码(在一个简单的库实现中)开始看起来像这样:

err = callToUnderlyingLibrary1();
if (err!=0) {
printf ("blah %d\n", err);
...
}

err = callToUnderlyingLibrary2();
if (err!=0) {
printf ("blah %d\n", err);
...
}

err = callToUnderlyingLibrary3();
if (err!=0) {
printf ("blah %d\n", err);
...
}

这很麻烦,很难看。有一个更好的方法吗 ?也许使用C预处理器?我想的是:

CHECK callToUnderlyingLibrary1();
CHECK callToUnderlyingLibrary2();
CHECK callToUnderlyingLibrary3();

其中CHECK宏调用函数并进行基本错误检查。

是否有首选的惯用方法来处理这个问题?

7 个答案:

答案 0 :(得分:15)

通常,在C中,使用goto进行错误处理:

int foo()
{
    if (Function1() == ERROR_CODE) goto error;
    ...
    struct bar *x = acquire_structure;
    ...
    if (Function2() == ERROR_CODE) goto error0;
    ...

    release_structure(x);
    return 0;

error0:
    release_structure(x);

error:
    return -1;
}

这可以通过宏和更聪明的指令流来改进(以避免重复清理代码),但我希望你明白这一点。

答案 1 :(得分:13)

另一种基于宏的方法,您可以使用它来轻松缓解C中的缺点:

#define CHECK(x) do { \
  int retval = (x); \
  if (retval != 0) { \
    fprintf(stderr, "Runtime error: %s returned %d at %s:%d", #x, retval, __FILE__, __LINE__); \
    return /* or throw or whatever */; \
  } \
} while (0)

然后调用它,你有:

CHECK(doSomething1());
CHECK(doSomething2());
// etc.

对于奖励积分,您可以轻松扩展CHECK宏以获取第二个参数y,即失败时该怎么做:

#define CHECK(x, y) do { \
  int retval = (x); \
  if (retval != 0) { \
    fprintf(stderr, "Runtime error: %s returned %d at %s:%d", #x, retval, __FILE__, __LINE__); \
    y; \
  } \
} while (0)

// We're returning a different error code
CHECK(someFunction1(foo), return someErrorCode);
// We're actually calling it from C++ and can throw an exception
CHECK(someFunction2(foo), throw SomeException("someFunction2 failed")):

答案 2 :(得分:6)

我认为您应该查看异常和异常处理。 http://www.cplusplus.com/doc/tutorial/exceptions/

try{    
    callToUnderlyingLibrary1();
    callToUnderlyingLibrary2();
    callToUnderlyingLibrary3();
}catch(exception& e)
    //Handle exception
}

如果出现错误,您的库函数可能会抛出异常

答案 3 :(得分:3)

这是一个命题,你可能喜欢也可能不喜欢它:

  • 使您的函数在失败时返回0,在成功时返回其他内容
  • 如果您的函数出现问题,请让它们为错误代码设置全局(或静态)变量(如errno
  • 创建一个die()函数,根据错误代码(或任何你想要它做的事情)打印错误。
  • 使用do_something(foo, bar) || die("Argh...");
  • 调用您的函数

答案 4 :(得分:1)

你可以做你所说的,这是一些基本的宏观:

#define CHECK(x) (err = x()); \
                 if (err) { \
                      printf("blah %d on line %d of file %s\n", err, __LINE__, __FILE__); \
                 } \
                 else (void)0

你可以像

一样使用它
int err = 0;
CHECK(callToUnderlyingLibrary1); // don't forget the semicolon at the end
CHECK(callToUnderlyingLibrary2);
CHECK(callToUnderlyingLibrary3);

答案 5 :(得分:1)

我更喜欢Alexandra C.的goto-approach的变体:

int foo()
{
    int rv = 0;
    struct bar *x = NULL;
    struct bar *y = NULL;
    rv = Function1();
    if (rv != OK){
      goto error;
    }
    //...
    x = acquire_structure();
    if (x==NULL){
      rv = ERROR_MEMORY;
      goto error;
    }
    //...
    rv = Function2();
    if (rv != OK){
      goto error;
    }
    //...
    y = acquire_structure();
    if (y==NULL){
      rv = ERROR_MEMORY;
      goto error;
    }
    //...

    rv = release_structure(x);
    x = NULL;
    if (rv != OK){
      goto error;
    }
    rv = release_structure(y);
    y = NULL;
    if (rv != OK){
      goto error;
    }
    return OK;

error:
    if (x!=NULL){
      release_structure(x);
    }
    return rv;
}

当您使用多个goto-destination时,很容易将它们混合起来。或者您可能会移动变量的初始化,但忘记更新gotos。并且很难测试所有 C方法失败的方式。

我更喜欢有一个执行所有清理的goto-destination。我发现这样可以更容易避免错误。

答案 6 :(得分:-4)

没有'转到',在函数中只使用1'返回'。这是优雅的代码。

恕我直言,OP的问题点和所有答案都在谈论FANCY技术。花哨的代码只是一种眼睛糖果。