在纯C程序中,有什么好的错误处理习惯用法吗?
回到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
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循环的含义