尝试C中的catch语句

今天我想到了另一种语言中存在的try / catch块。 Googlesearch了一段时间,但没有结果。 据我所知,在C中没有这样的事情。但是,有没有办法“模拟”它们呢?
当然,有断言和其他技巧,但没有像try / catch,也捕捉到exception。 谢谢

C本身不支持exception,但你可以使用setjmplongjmp调用来模拟它们。

 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); } 

这个网站有一个很好的教程,介绍如何用setjmplongjmp来模拟exception

在C中使用goto来处理类似的错误。
这是最接近的例外,你可以在C.

在C99中,可以使用setjmp / longjmp作为非本地控制stream。

在一个范围内,在多个资源分配和多个出口存在的情况下,C的通用结构化编码模式使用goto ,就像在这个例子中一样 。 这与C ++如何在引擎盖下实现自动对象的析构函数调用类似,如果坚持这一点,即使在复杂的函数中,也应该允许一定程度的清洁。

这可以在C中使用setjmp/longjmp完成。P99有一个相当舒适的工具集,这也与C11的新线程模型一致。

虽然其他一些答案已经使用setjmplongjmp覆盖了简单的例子,但在真正的应用程序中,有两个问题确实很重要。

  1. 嵌套的try / catch块。 对你的jmp_buf使用一个全局variables将使这些不起作用。
  2. 线程。 对你来说,一个单一的全局variablesjmp_buf会在这种情况下造成各种各样的痛苦。

解决这个问题的办法是维护一个线程局部的jmp_buf栈,它随着你的进程而更新。 (我认为这是lua在内部使用的)。

所以,而不是(从Jare​​dPar的真棒回答)

 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); } 

你会使用像这样的东西:

 #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); } 

再一个更实际的版本将包括一些方法来存储错误信息到exception_state ,更好地处理MAX_EXCEPTION_DEPTH (也许使用realloc来增长缓冲区,或类似的东西)。

免责声明:上面的代码是没有任何testing写的。 这完全是你如何构build事物的想法。 不同的系统和不同的编译器将需要以不同的方式实现线程本地存储。 代码可能包含编译错误和逻辑错误 – 所以尽pipe你可以自由使用它,但在使用之前进行testing;)

快速的谷歌search产生kludgey解决scheme,如使用setjmp / longjmp像其他人所提到的。 没有像C ++ / Java的try / catch那样简单明了。 我比较偏爱Ada的exception处理。

检查一切与if语句:)

好吧,我忍不住回答这个。 让我先说我不认为用C来模拟这个是个好主意,因为它对于C来说确实是一个外国概念。

我们可以使用滥用预处理器和本地堆栈variables来使用限制版本的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; 

版本1只是一个本地抛出(不能离开函数的范围)。 它依赖于C99在代码中声明variables的能力(如果try是函数中的第一项,它应该在C89中工作)。

这个函数只是一个局部variables,所以它知道是否有错误,并使用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; } 

这可以解释为:

 int main(void) { bool HadError=false; { printf("One\n"); HadError=true; goto ExitJmp; printf("Two\n"); } ExitJmp: if(HadError) { printf("Error\n"); } return 0; } 

版本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); 

版本2是复杂得多,但基本上是一样的。 它使用当前函数跳到try块。 try块然后使用一个if / else来跳过代码块到catch块,它检查局部variables是否应该捕获。

这个例子再次扩展了:

 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; } 

这使用全局指针,所以longjmp()知道上次运行的是什么。 我们正在滥用堆栈,所以子函数也可以有一个try / catch块。

使用这个代码有一些不利的方面(但是一个有趣的心理练习):

  • 它不会释放分配的内存,因为没有调用解构器。
  • 在一个范围内不能有超过1个try / catch(没有嵌套)
  • 你实际上不能像C ++那样抛出exception或其他数据
  • 根本不是线程安全的
  • 你正在设置其他程序员的失败,因为他们可能不会注意到黑客攻击,并尝试使用它们,如C + + try / catch块。

警告:以下是不是很好,但它的工作。

 #include <stdio.h> #include <stdlib.h> typedef struct { unsigned int id; char *name; char *msg; } error; #define _printerr(e, s, ...) fprintf(stderr, "\033[1m\033[37m" "%s:%d: " "\033[1m\033[31m" e ":" "\033[1m\033[37m" " '%s_error' " "\033[0m" s "\n", __FILE__, __LINE__, (*__err)->name, ##__VA_ARGS__) #define printerr(s, ...) _printerr("error", s, ##__VA_ARGS__) #define printuncaughterr() _printerr("uncaught error", "%s", (*__err)->msg) #define _errordef(n, _id) \ error* new_##n##_error_msg(char* msg) { \ error* self = malloc(sizeof(error)); \ self->id = _id; \ self->name = #n; \ self->msg = msg; \ return self; \ } \ error* new_##n##_error() { return new_##n##_error_msg(""); } #define errordef(n) _errordef(n, __COUNTER__ +1) #define try(try_block, err, err_name, catch_block) { \ error * err_name = NULL; \ error ** __err = & err_name; \ void __try_fn() try_block \ __try_fn(); \ void __catch_fn() { \ if (err_name == NULL) return; \ unsigned int __##err_name##_id = new_##err##_error()->id; \ if (__##err_name##_id != 0 && __##err_name##_id != err_name->id) \ printuncaughterr(); \ else if (__##err_name##_id != 0 || __##err_name##_id != err_name->id) \ catch_block \ } \ __catch_fn(); \ } #define throw(e) { *__err = e; return; } _errordef(any, 0) 

用法:

 errordef(my_err1) errordef(my_err2) try ({ printf("Helloo\n"); throw(new_my_err1_error_msg("hiiiii!")); printf("This will not be printed!\n"); }, /*catch*/ any, e, { printf("My lovely error: %s %s\n", e->name, e->msg); }) printf("\n"); try ({ printf("Helloo\n"); throw(new_my_err2_error_msg("my msg!")); printf("This will not be printed!\n"); }, /*catch*/ my_err2, e, { printerr("%s", e->msg); }) printf("\n"); try ({ printf("Helloo\n"); throw(new_my_err1_error()); printf("This will not be printed!\n"); }, /*catch*/ my_err2, e, { printf("Catch %s if you can!\n", e->name); }) 

输出:

 Helloo My lovely error: my_err1 hiiiii! Helloo /home/naheel/Desktop/aa.c:28: error: 'my_err2_error' my msg! Helloo /home/naheel/Desktop/aa.c:38: uncaught error: 'my_err1_error' 

请记住,这是使用嵌套函数和__COUNTER__ 。 如果你使用的是gcc,你会安全的。

也许不是一个主要的语言(不幸的是),但在APL,theEA的操作(代表执行备用)。

用法:'Y'⎕EA'X'其中X和Y是作为string或函数名提供的代码片断。

如果X遇到错误,Y(通常是error handling)将被执行。

如果你在C中使用Win32,你可以利用它的结构化exception处理(SEH)来模拟try / catch。

如果您在不支持setjmp()longjmp()平台中使用C,请查看pjsip库的exception处理 ,它提供了自己的实现

Redis使用goto模拟try / catch,恕我直言,它很干净和优雅:

 /* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success. */ int rdbSave(char *filename) { char tmpfile[256]; FILE *fp; rio rdb; int error = 0; snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid()); fp = fopen(tmpfile,"w"); if (!fp) { redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s", strerror(errno)); return REDIS_ERR; } rioInitWithFile(&rdb,fp); if (rdbSaveRio(&rdb,&error) == REDIS_ERR) { errno = error; goto werr; } /* Make sure data will not remain on the OS's output buffers */ if (fflush(fp) == EOF) goto werr; if (fsync(fileno(fp)) == -1) goto werr; if (fclose(fp) == EOF) goto werr; /* Use RENAME to make sure the DB file is changed atomically only * if the generate DB file is ok. */ if (rename(tmpfile,filename) == -1) { redisLog(REDIS_WARNING,"Error moving temp DB file on the final destination: %s", strerror(errno)); unlink(tmpfile); return REDIS_ERR; } redisLog(REDIS_NOTICE,"DB saved on disk"); server.dirty = 0; server.lastsave = time(NULL); server.lastbgsave_status = REDIS_OK; return REDIS_OK; werr: fclose(fp); unlink(tmpfile); redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno)); return REDIS_ERR; } 

这是在C中执行error handling的另一种方式,比使用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; } 

如果你愿意,你可以重新定义通用代码,有效地实现你自己的error handling系统。

 /* 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); } 

那么这个例子就成了

 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; }