C:你如何模拟’例外’?
我来自C#背景,但我现在正在学习C语言。 在C#中,当想要发出错误信号时,就会抛出exception。 但你在C做什么?
比如说你有一个带有push
和pop
function的堆栈。 在pop
,什么是表示堆栈为空的最佳方式? 你从这个function返回什么?
double pop(void) { if(sp > 0) return val[--sp]; else { printf("error: stack empty\n"); return 0.0; } }
K&R的第77页示例( 上面的代码 )返回0.0
。 但是如果用户在堆栈上先推了0.0
,那么如何知道堆栈是否为空或者是否返回了正确的值呢?
C中的exception行为是通过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; }
显然不理想,但这是C的局限性。
此外,如果你走这条路,你可能想要为你的错误代码定义符号常量(或使用一些标准的代码 ),这样用户就可以区分“堆栈空”和“你给我一个空指针,dumbass ”。
您可以在longjmp / setjmp之上构建一个exception系统: 使用Longjmp和Setjmp的C中的exception 。 它实际上运作得很好,这篇文章也很好读。 以下是您使用链接文章中的exception系统时代码的外观:
TRY { ... THROW(MY_EXCEPTION); /* Unreachable */ } CATCH(MY_EXCEPTION) { ... } CATCH(OTHER_EXCEPTION) { ... } FINALLY { ... }
使用一些小宏可以做些什么,对吧? 同样令人惊讶的是,如果您还不知道宏的作用,弄清楚发生了什么事情是多么困难。
longjmp / setjmp是可移植的:C89,C99和POSIX.1-2001指定setjmp()
。
但请注意,与C#或C ++中的“真实”exception相比,以这种方式实现的exception仍然存在一些限制。 一个主要问题是只有您的代码才能与此exception系统兼容。 由于C中没有已建立的exception标准,系统和第三方库只是不能与您自己开发的exception系统进行最佳互操作。 尽管如此,这有时会变成一个有用的黑客。
我不建议在严重的代码中使用它,而不是你自己的程序员应该使用它。 如果你不确切知道发生了什么,那就太容易用脚射击自己了。 线程,资源管理和信号处理是非玩具程序在尝试使用longjmp“例外”时会遇到的问题区域。
你有几个选择:
1)魔术错误值。 由于你描述的原因,并不总是足够好。 我想在理论上这个案例你可以返回一个NaN,但我不推荐它。
2)定义当堆栈为空时弹出是无效的。 然后你的代码只是假设它是非空的(并且如果它是未定义的),或者断言。
3)更改function的签名,以便指示成功或失败:
int pop(double *dptr) { if(sp > 0) { *dptr = val[--sp]; return 0; } else { return 1; } }
将其记录为“如果成功,则返回0并将值写入dptr指向的位置。失败时,返回非零值。”
(可选)您可以使用返回值或errno
来指示失败的原因,但对于此特定示例,只有一个原因。
4)通过指针将“exception”对象传递给每个函数,并在失败时为其写入一个值。 然后,呼叫者根据他们如何使用返回值来检查它。 这很像使用“errno”,但没有它是线程范围的值。
5)正如其他人所说,用setjmp / longjmp实现exception。 它是可行的,但需要在任何地方传递一个额外的参数(longjmp的目标在失败时执行),或者将其隐藏在全局变量中。 它还使典型的C风格资源处理成为一场噩梦,因为如果你持有一个你负责释放的资源,你就不能调用可能超出你的堆栈级别的任何东西。
一种方法是指定如果堆栈为空,pop()具有未定义的行为。 然后,您必须提供一个is_empty()函数,可以调用该函数来检查堆栈。
另一种方法是使用C ++,它有例外:-)
这实际上是尝试使用魔术值和简单可疑的界面设计重载返回类型的邪恶的完美示例。
我可能用来消除示例中的歧义(因此需要“exception行为”)的一个解决方案是定义一个正确的返回类型:
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
这种情况一直都在发生,但在C ++中为避免这种模糊,通常只会返回一个
std::pair
bool是成功指标的地方 – 看一下:
std::set<...>::insert std::map<...>::insert
或者在接口中添加一个double*
并返回一个(n UNOVERLOADED!)返回码,比如表示成功的枚举。
当然,没有必要返回struct popRC
的大小。 它可能是
enum{FAIL,SUCCESS};
但是,由于尺寸可能对pop’er有用,你可以使用它。
顺便说一句,我衷心同意结构堆栈接口应该有
int empty(struct stack* pS){ return (pS->size == 0) ? 1 : 0; }
在这种情况下,你通常会做一个
- 把它留给来电者。 例如,由调用者知道pop()是否安全(例如在弹出堆栈之前调用stack-> is_empty()函数),如果调用者搞砸了,那就是他的错,祝你好运。
- 通过out参数或返回值发出错误信号。
比如你要么
double pop(int *error) { if(sp > 0) { return val[--sp]; *error = 0; } else { *error = 1; printf("error: stack empty\n"); return 0.0; }
}
要么
int pop(double *d) { if(sp > 0) { *d = val[--sp]; return 0; } else { return 1; } }
在C语言中没有等同于例外。您必须设计函数签名以返回错误信息,如果这是您想要的。
C中可用的机制是:
- 使用setjmp / longjmp的非本地getos
- 信号
但是,这些语义都没有远远类似于C#(或C ++)exception的语义。
你可以返回一个指向double的指针:
- 非NULL – >有效
- NULL – >无效
1)您返回一个标志值以显示它失败,或者您使用TryGet语法,其中返回是成功的布尔值,而值通过输出参数传递。
2)如果这是在Windows下,则使用类似“_try”的语法,有一种操作系统级的纯Cforms的exception,称为Structed Exception Handling。 我提到它,但我不推荐这种情况。
还没有人提到的东西,但它很丑陋:
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"); }
这里已经有了一些很好的答案,只是想提一下接近“exception”的东西,可以使用宏来完成,就像在令人敬畏的MinUnit中做的那样 (这只会给调用者函数返回“exception”) 。
setjmp
, longjmp
和宏。 它已经完成了很多次 – 我所知道的最古老的实现是Eric Roberts和Mark vanderVoorde–但我目前使用的是Dave Hanson的C接口和实现的一部分,并且不受普林斯顿的影响。