C:如何模拟';例外情况';?
我来自C#背景,但我现在正在学习C。在C#中,当一个人想要发出错误发生的信号时,你抛出一个异常。但是你在C语言中做什么 例如,假设您有一个具有C:如何模拟';例外情况';?,c,exception-handling,stack,C,Exception Handling,Stack,我来自C#背景,但我现在正在学习C。在C#中,当一个人想要发出错误发生的信号时,你抛出一个异常。但是你在C语言中做什么 例如,假设您有一个具有push和pop功能的堆栈。在pop期间,发出堆栈为空的信号的最佳方式是什么?从该函数返回什么 double pop(void) { if(sp > 0) return val[--sp]; else { printf("error: stack empty\n"); return 0.
push
和pop
功能的堆栈。在pop
期间,发出堆栈为空的信号的最佳方式是什么?从该函数返回什么
double pop(void)
{
if(sp > 0)
return val[--sp];
else {
printf("error: stack empty\n");
return 0.0;
}
}
第77页的示例(上面的代码)返回一个
0.0
。但是,如果用户在堆栈的前面按下了0.0
,您如何知道堆栈是否为空或是否返回了正确的值呢?一种方法是指定如果堆栈为空,则pop()具有未定义的行为。然后必须提供一个is_empty()函数,可以调用该函数来检查堆栈
另一种方法是使用C++,它有例外:-< /p>< P>异常,类似于C中的行为是通过但是,这里真正需要的是一个错误代码。如果所有值都可能可返回,那么您可能希望输入一个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;
}
显然,这并不理想,但这就是C语言的局限性
此外,如果你走这条路,你可能想为你的错误代码定义符号常量(或使用其中的一些),这样用户就可以区分“堆栈为空”和“你给了我一个空指针,笨蛋”。你可以将指针返回到double:
- 非空->有效
- 空->无效
int pop(double *dptr)
{
if(sp > 0) {
*dptr = val[--sp];
return 0;
} else {
return 1;
}
}
将其记录为“如果成功,则返回0并将值写入dptr指向的位置。如果失败,则返回非零值。”
或者,您可以使用返回值或errno
来指示失败的原因,尽管对于这个特定示例只有一个原因
4) 通过指针将“exception”对象传递给每个函数,并在失败时向其写入一个值。然后调用者根据他们如何使用返回值来检查它。这很像使用“errno”,但它不是线程范围的值
5) 正如其他人所说,使用setjmp/longjmp实现异常。这是可行的,但需要在任何地方传递一个额外的参数(失败时要执行的longjmp的目标),或者将其隐藏在全局中。它还使典型的C风格资源处理成为一场噩梦,因为如果您持有一个负责释放的资源,您不能调用任何可能跳出堆栈级别的东西。您可以在longjmp/setjmp:之上构建一个异常系统。它实际上运行得很好,这篇文章也是一本很好的读物。如果您使用链接文章中的异常系统,代码的外观如下:
TRY {
...
THROW(MY_EXCEPTION);
/* Unreachable */
} CATCH(MY_EXCEPTION) {
...
} CATCH(OTHER_EXCEPTION) {
...
} FINALLY {
...
}
你能用一个小宏做什么真是太神奇了,对吧?同样令人惊讶的是,如果你还不知道宏是做什么的,那么弄清楚到底发生了什么是多么困难
可移植:C89、C99和POSIX.1-2001指定setjmp()
注意到,与C或C++中的“实际”异常相比,以这种方式实现的异常仍然有一些限制。一个主要问题是,只有您的代码才能与此异常系统兼容。由于C语言中没有针对异常的既定标准,系统库和第三方库无法与您自己开发的异常系统进行最佳互操作。尽管如此,这有时会成为一种有用的黑客手段
我不建议在严肃的代码中使用这个,除了你自己,其他程序员应该与之合作。如果你不知道到底发生了什么事,那么用这个射自己的脚太容易了。线程、资源管理和信号处理是非玩具程序在尝试使用longjmp“异常”时会遇到的问题领域。在straight C中没有与异常等价的功能。如果您想要返回错误信息,则必须设计函数签名 C中可用的机制有:
- 具有setjmp/longjmp的非本地gotos
- 信号
但是,所有这些都没有与C#(或C++)异常极为相似的语义
- 交给打电话的人吧。e、 g.由调用方决定是否安全地弹出()(例如,在弹出堆栈之前调用一个stack->is_empty()函数),如果调用方搞错了,那是他的错,祝你好运
- 通过输出参数或返回值发出错误信号李>
double pop(int *error)
{
if(sp > 0) {
return val[--sp];
*error = 0;
} else {
*error = 1;
printf("error: stack empty\n");
return 0.0;
}
}
或
这里已经有了一些很好的答案,只是想提一下,类似于“异常”的事情可以通过使用宏来完成,就像在awesome中所做的那样(这只会将“异常”返回给调用方函数)。1)您返回一个标志值以显示它失败,或者使用TryGet语法,其中返回值为布尔值,表示成功,而值通过输出参数传递
2) 如果这是在Windows下,则有一个操作系统级别的纯C形式的异常,称为结构化异常处理,使用类似“\u try”的语法。我提到了它,但我不建议在这种情况下使用它。这实际上是一个完美的例子,说明了试图用神奇的值重载返回类型的弊端,而只是简单的有问题的接口设计 我可以使用一种解决方案来消除示例中的歧义(因此需要“类似异常的行为”),即定义
int pop(double *d)
{
if(sp > 0) {
*d = val[--sp];
return 0;
} else {
return 1;
}
}
struct stack{
double* pData;
uint32 size;
};
struct popRC{
double value;
uint32 size_before_pop;
};
popRC pop(struct stack* pS){
popRC rc;
rc.size=pS->size;
if(rc.size){
--pS->size;
rc.value=pS->pData[pS->size];
}
return rc;
}
popRC rc = pop(&stack);
if(rc.size_before_pop!=0){
....use rc.value
std::pair<something,bool>
std::set<...>::insert
std::map<...>::insert
enum{FAIL,SUCCESS};
int empty(struct stack* pS){
return (pS->size == 0) ? 1 : 0;
}
int ok=0;
do
{
/* Do stuff here */
/* If there is an error */
break;
/* If we got to the end without an error */
ok=1;
} while(0);
if (ok == 0)
{
printf("Fail.\n");
}
else
{
printf("Ok.\n");
}