我今天想到了另一种语言中存在的try/catch块.谷歌搜索了一段时间,但没有结果.据我所知,在C中没有try/catch这样的东西.但是,有没有办法"模拟"它们?
当然,有断言和其他技巧,但没有像try/catch,也可以捕获引发的异常.谢谢
Jar*_*Par 82
C本身不支持异常,但您可以使用setjmp
和longjmp
调用它们来模拟它们.
static jmp_buf s_jumpBuffer;
void Example() {
if (setjmp(s_jumpBuffer)) {
// The longjmp was executed and returned control here
printf("Exception happened here\n");
} else {
// Normal code execution starts here
Test();
}
}
void Test() {
// Rough equivalent of `throw`
longjmp(s_jumpBuffer, 42);
}
Run Code Online (Sandbox Code Playgroud)
这个网站有一个关于如何使用setjmp
和模拟异常的很好的教程longjmp
Alo*_*ave 23
您在C中使用goto进行类似的错误处理情况.
这是您可以在C中获得的最接近的例外情况.
Pau*_*son 11
好吧,我无法抗拒回复此事.我先说我不认为在C中模拟这个是个好主意,因为它确实是C的外国概念.
我们可以使用滥用预处理器和本地堆栈变量来使用有限版本的C++ try/throw/catch.
版本1(本地范围抛出)
#include <stdbool.h>
#define try bool __HadError=false;
#define catch(x) ExitJmp:if(__HadError)
#define throw(x) __HadError=true;goto ExitJmp;
Run Code Online (Sandbox Code Playgroud)
版本1仅是本地投掷(不能离开函数的范围).它确实依赖于C99在代码中声明变量的能力(如果try是函数中的第一个东西,它应该在C89中工作).
此函数只生成一个局部变量,因此它知道是否存在错误并使用goto跳转到catch块.
例如:
#include <stdio.h>
#include <stdbool.h>
#define try bool __HadError=false;
#define catch(x) ExitJmp:if(__HadError)
#define throw(x) __HadError=true;goto ExitJmp;
int main(void)
{
try
{
printf("One\n");
throw();
printf("Two\n");
}
catch(...)
{
printf("Error\n");
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这有点类似于:
int main(void)
{
bool HadError=false;
{
printf("One\n");
HadError=true;
goto ExitJmp;
printf("Two\n");
}
ExitJmp:
if(HadError)
{
printf("Error\n");
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
版本2(范围跳跃)
#include <stdbool.h>
#include <setjmp.h>
jmp_buf *g__ActiveBuf;
#define try jmp_buf __LocalJmpBuff;jmp_buf *__OldActiveBuf=g__ActiveBuf;bool __WasThrown=false;g__ActiveBuf=&__LocalJmpBuff;if(setjmp(__LocalJmpBuff)){__WasThrown=true;}else
#define catch(x) g__ActiveBuf=__OldActiveBuf;if(__WasThrown)
#define throw(x) longjmp(*g__ActiveBuf,1);
Run Code Online (Sandbox Code Playgroud)
版本2更复杂,但基本上以相同的方式工作.它使用当前函数的长跳转到try块.然后try块使用if/else将代码块跳过catch块,该块检查局部变量以查看它是否应该捕获.
该示例再次扩展:
jmp_buf *g_ActiveBuf;
int main(void)
{
jmp_buf LocalJmpBuff;
jmp_buf *OldActiveBuf=g_ActiveBuf;
bool WasThrown=false;
g_ActiveBuf=&LocalJmpBuff;
if(setjmp(LocalJmpBuff))
{
WasThrown=true;
}
else
{
printf("One\n");
longjmp(*g_ActiveBuf,1);
printf("Two\n");
}
g_ActiveBuf=OldActiveBuf;
if(WasThrown)
{
printf("Error\n");
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这使用全局指针,因此longjmp()知道上次运行的尝试.我们正在使用滥用堆栈,因此子函数也可以有一个try/catch块.
使用此代码有许多缺点(但这是一个有趣的心理练习):
虽然一些其他的答案都使用覆盖简单的情况setjmp
,并longjmp
在实际的应用中有两个关注真正重要的.
jmp_buf
意志使用单个全局变量使这些不起作用.jmp_buf
在这种情况下,单个全局变量将导致各种各样的痛苦.这些解决方案是维护一个随时jmp_buf
更新的线程局部堆栈.(我认为这是lua在内部使用的).
所以不要这样(来自JaredPar的精彩答案)
static jmp_buf s_jumpBuffer;
void Example() {
if (setjmp(s_jumpBuffer)) {
// The longjmp was executed and returned control here
printf("Exception happened\n");
} else {
// Normal code execution starts here
Test();
}
}
void Test() {
// Rough equivalent of `throw`
longjump(s_jumpBuffer, 42);
}
Run Code Online (Sandbox Code Playgroud)
你会使用类似的东西:
#define MAX_EXCEPTION_DEPTH 10;
struct exception_state {
jmp_buf s_jumpBuffer[MAX_EXCEPTION_DEPTH];
int current_depth;
};
int try_point(struct exception_state * state) {
if(current_depth==MAX_EXCEPTION_DEPTH) {
abort();
}
int ok = setjmp(state->jumpBuffer[state->current_depth]);
if(ok) {
state->current_depth++;
} else {
//We've had an exception update the stack.
state->current_depth--;
}
return ok;
}
void throw_exception(struct exception_state * state) {
longjump(state->current_depth-1,1);
}
void catch_point(struct exception_state * state) {
state->current_depth--;
}
void end_try_point(struct exception_state * state) {
state->current_depth--;
}
__thread struct exception_state g_exception_state;
void Example() {
if (try_point(&g_exception_state)) {
catch_point(&g_exception_state);
printf("Exception happened\n");
} else {
// Normal code execution starts here
Test();
end_try_point(&g_exception_state);
}
}
void Test() {
// Rough equivalent of `throw`
throw_exception(g_exception_state);
}
Run Code Online (Sandbox Code Playgroud)
再一个更现实的版本将包括一些方法将错误信息存储到exception_state
更好的处理MAX_EXCEPTION_DEPTH
(可能使用realloc来增长缓冲区,或类似的东西).
免责声明:上面的代码是在没有任何测试的情况下编写的.这纯粹是让你了解如何构建事物.不同的系统和不同的编译器需要以不同的方式实现线程本地存储.代码可能包含编译错误和逻辑错误 - 所以当你可以随意使用它时,在使用之前测试它;)
这是在 C 中进行错误处理的另一种方法,它比使用 setjmp/longjmp 性能更高。不幸的是,它不适用于 MSVC,但如果仅使用 GCC/Clang 是一种选择,那么您可能会考虑它。具体来说,它使用“标签作为值”扩展,它允许您获取标签的地址,将其存储在值中,然后无条件跳转到该值。我将用一个例子来展示它:
GameEngine *CreateGameEngine(GameEngineParams const *params)
{
/* Declare an error handler variable. This will hold the address
to jump to if an error occurs to cleanup pending resources.
Initialize it to the err label which simply returns an
error value (NULL in this example). The && operator resolves to
the address of the label err */
void *eh = &&err;
/* Try the allocation */
GameEngine *engine = malloc(sizeof *engine);
if (!engine)
goto *eh; /* this is essentially your "throw" */
/* Now make sure that if we throw from this point on, the memory
gets deallocated. As a convention you could name the label "undo_"
followed by the operation to rollback. */
eh = &&undo_malloc;
/* Now carry on with the initialization. */
engine->window = OpenWindow(...);
if (!engine->window)
goto *eh; /* The neat trick about using approach is that you don't
need to remember what "undo" label to go to in code.
Simply go to *eh. */
eh = &&undo_window_open;
/* etc */
/* Everything went well, just return the device. */
return device;
/* After the return, insert your cleanup code in reverse order. */
undo_window_open: CloseWindow(engine->window);
undo_malloc: free(engine);
err: return NULL;
}
Run Code Online (Sandbox Code Playgroud)
如果您愿意,您可以重构定义中的通用代码,有效地实现您自己的错误处理系统。
/* Put at the beginning of a function that may fail. */
#define declthrows void *_eh = &&err
/* Cleans up resources and returns error result. */
#define throw goto *_eh
/* Sets a new undo checkpoint. */
#define undo(label) _eh = &&undo_##label
/* Throws if [condition] evaluates to false. */
#define check(condition) if (!(condition)) throw
/* Throws if [condition] evaluates to false. Then sets a new undo checkpoint. */
#define checkpoint(label, condition) { check(condition); undo(label); }
Run Code Online (Sandbox Code Playgroud)
那么例子就变成了
GameEngine *CreateGameEngine(GameEngineParams const *params)
{
declthrows;
/* Try the allocation */
GameEngine *engine = malloc(sizeof *engine);
checkpoint(malloc, engine);
/* Now carry on with the initialization. */
engine->window = OpenWindow(...);
checkpoint(window_open, engine->window);
/* etc */
/* Everything went well, just return the device. */
return device;
/* After the return, insert your cleanup code in reverse order. */
undo_window_open: CloseWindow(engine->window);
undo_malloc: free(engine);
err: return NULL;
}
Run Code Online (Sandbox Code Playgroud)