And*_*ech 14 c stack exception-handling
我来自C#背景,但我现在正在学习C. 在C#中,当想要发出错误信号时,就会抛出异常.但你在C做什么?
比如说你有一个堆栈push
和pop
函数.什么是在a期间表示堆栈为空的最佳方式pop
?你从这个功能返回什么?
double pop(void)
{
if(sp > 0)
return val[--sp];
else {
printf("error: stack empty\n");
return 0.0;
}
}
Run Code Online (Sandbox Code Playgroud)
K&R的第77页示例(上面的代码)返回一个0.0
.但是,如果用户0.0
在堆栈上推送了更早的内容,您如何知道堆栈是否为空或是否返回了正确的值?
Tyl*_*nry 15
C中的异常行为是通过setjmp/longjmp完成的.但是,您真正想要的是错误代码.如果所有值都可能是可返回的,那么您可能希望将out参数作为指针,并使用它来返回值,如下所示:
int pop(double* outval)
{
if(outval == 0) return -1;
if(sp > 0)
*outval = val[--sp];
else {
printf("error: stack empty\n");
return -1;
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
显然不是很理想,但这是C的限制.
此外,如果你走这条路,你可能想要为你的错误代码定义符号常量(或使用一些标准的代码),这样用户就可以区分"堆栈空"和"你给我一个空指针,dumbass ".
Vil*_*ari 10
您可以在longjmp/setjmp之上构建一个异常系统:使用Longjmp和Setjmp的C中的异常.它实际上工作得很好,这篇文章也很好读.如果您使用链接文章中的异常系统,那么代码就是这样的:
TRY {
...
THROW(MY_EXCEPTION);
/* Unreachable */
} CATCH(MY_EXCEPTION) {
...
} CATCH(OTHER_EXCEPTION) {
...
} FINALLY {
...
}
Run Code Online (Sandbox Code Playgroud)
使用一些小宏可以做些什么,对吧?同样令人惊讶的是,如果您还不知道宏的作用,弄清楚发生了什么是多么困难.
longjmp/setjmp是可移植的:C89,C99和POSIX.1-2001指定setjmp()
.
但请注意,与C#或C++中的"实际"异常相比,以这种方式实现的异常仍然存在一些限制.一个主要问题是只有您的代码才能与此异常系统兼容.由于C中没有既定的例外标准,系统和第三方库只是不能与您自己开发的异常系统进行最佳互操作.不过,这有时会变成一个有用的黑客.
我不建议在严肃的代码中使用它,而不是你自己的程序员应该使用它.如果您不确切知道发生了什么,那么用这种方式射击自己就太容易了.线程,资源管理和信号处理是非玩具程序在尝试使用longjmp"例外"时会遇到的问题区域.
你有几个选择:
1)魔术错误值.由于你描述的原因,并不总是足够好.我想在理论上这个案例你可以返回一个NaN,但我不推荐它.
2)定义当堆栈为空时弹出是无效的.然后你的代码只是假设它是非空的(并且如果它是未定义的),或者断言.
3)更改功能的签名,以便指示成功或失败:
int pop(double *dptr)
{
if(sp > 0) {
*dptr = val[--sp];
return 0;
} else {
return 1;
}
}
Run Code Online (Sandbox Code Playgroud)
将其记录为"如果成功,则返回0并将值写入dptr指向的位置.失败时,返回非零值."
或者,您可以使用返回值或errno
指示失败的原因,但对于此特定示例,只有一个原因.
4)通过指针将"异常"对象传递给每个函数,并在失败时为其写入一个值.然后,呼叫者根据他们如何使用返回值来检查它.这很像使用"errno",但没有它是线程范围的值.
5)正如其他人所说,用setjmp/longjmp实现异常.它是可行的,但需要在任何地方传递额外的参数(longjmp的目标在失败时执行),或者将其隐藏在全局变量中.它还使典型的C风格资源处理成为一场噩梦,因为如果你持有一个你负责释放的资源,你就不能调用任何可能超出你的堆栈级别的东西.