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

在纯C程序中,有什么好的错误处理习惯用法吗?,c,error-handling,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; int do_something_complicated(...) { ... err = f

回到C工作中

我的许多函数如下所示:

int err = do_something(arg1, arg2, arg3, &result);
int err = func1(...);
if (!err) {
    err = func2(...);
    if (!err) {
        err = func3(...);
    }
}
return err;
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 */
}
根据意图,结果由函数填充,返回值是调用的状态

黑暗的一面是你得到了一些像这样天真的东西:

int err = do_something(arg1, arg2, arg3, &result);
int err = func1(...);
if (!err) {
    err = func2(...);
    if (!err) {
        err = func3(...);
    }
}
return err;
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 */
}
我想我可以:

#define ERR(x) if (!err) { err = (x) }
int err = 0;
ERR(func1(...));
ERR(func2(...));
ERR(func3(...));
return err;
但这只适用于链接函数调用,而不是做其他工作

显然,java、C++、C++有很多对这些事情很有效的例外。
我只是好奇现在其他人在C程序中做什么,以及其他人是如何处理错误的。

你在
else
语句中做什么?如果没有,请尝试以下操作:

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

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

err = func3(...);

return err;
这样你就短路了整个函数,甚至不用担心下面的函数调用

编辑

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

两种典型模式:

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

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

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

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;

--

通常,需要在退出时进行清理的函数(例如,可用内存)的编写方式如下:

int err = do_something(arg1, arg2, arg3, &result);
int err = func1(...);
if (!err) {
    err = func2(...);
    if (!err) {
        err = func3(...);
    }
}
return err;
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声明为globals

或者,您可以使用宏来简单地传递参数。我建议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;
}
--


呸。我做完了。(疯狂的示例未经测试。某些详细信息可能已关闭。)

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

您可能会变得非常愚蠢,并继续:

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

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

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

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

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

现在是完全不同的事情

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

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"));
这为您提供了其他语言中异常类的许多优点(尽管调用者仍然需要处理错误-调用者必须检查错误代码,并且可能必须提前返回,但是他们不能做任何事情,或者几乎什么也不做,并且允许错误传播到调用方法链上,直到其中一个想要处理它,就像异常一样

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

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

i、 e


我最近看到的是这个idom:

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-any?).对于针对圈复杂度进行测试的项目,这是上帝派来的,因为它将所有初始化内容放在一起

相反的论点:

do/while循环的含义