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);
}
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);
}
/* 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;
}
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’
这是在 C 中执行错误处理的另一种方法,它比使用 setjmp/longjmp 性能更好。不幸的是,它不能与 MSVC 一起工作,但是如果只使用 GCC/Clang 是一个选项,那么您可以考虑使用它。具体来说,它使用了“ label as value”扩展,这允许您获取标签的地址,将其存储在一个值中,然后无条件地跳转到它。我将用一个例子来说明:
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;
}
如果您愿意,您可以在定义中重构公共代码,从而有效地实现您自己的错误处理系统。
/* 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;
}
在 C 语言中,您可以通过手动使用 if + goto 进行显式错误处理来“模拟”异常和自动“对象回收”。
我经常编写下面这样的 C 代码(归结为强调错误处理) :
#include <assert.h>
typedef int errcode;
errcode init_or_fail( foo *f, goo *g, poo *p, loo *l )
{
errcode ret = 0;
if ( ( ret = foo_init( f ) ) )
goto FAIL;
if ( ( ret = goo_init( g ) ) )
goto FAIL_F;
if ( ( ret = poo_init( p ) ) )
goto FAIL_G;
if ( ( ret = loo_init( l ) ) )
goto FAIL_P;
assert( 0 == ret );
goto END;
/* error handling and return */
/* Note that we finalize in opposite order of initialization because we are unwinding a *STACK* of initialized objects */
FAIL_P:
poo_fini( p );
FAIL_G:
goo_fini( g );
FAIL_F:
foo_fini( f );
FAIL:
assert( 0 != ret );
END:
return ret;
}
这是完全标准的 ANSI C,将错误处理从主线代码中分离出来,允许(手动)对初始化对象进行堆栈展开,就像 C + + 所做的那样,这里发生的事情完全是显而易见的。因为在每个点都显式地测试故障,所以在每个可能发生错误的地方插入特定的日志记录或错误处理会更容易。
如果您不介意使用一些宏魔术,那么您可以在使用堆栈跟踪记录错误之类的其他事情时使其更加简洁。例如:
#include <assert.h>
#include <stdio.h>
#include <string.h>
#define TRY( X, LABEL ) do { if ( ( X ) ) { fprintf( stderr, "%s:%d: Statement '%s' failed! %d, %s\n", __FILE__, __LINE__, #X, ret, strerror( ret ) ); goto LABEL; } while ( 0 )
typedef int errcode;
errcode init_or_fail( foo *f, goo *g, poo *p, loo *l )
{
errcode ret = 0;
TRY( ret = foo_init( f ), FAIL );
TRY( ret = goo_init( g ), FAIL_F );
TRY( ret = poo_init( p ), FAIL_G );
TRY( ret = loo_init( l ), FAIL_P );
assert( 0 == ret );
goto END;
/* error handling and return */
FAIL_P:
poo_fini( p );
FAIL_G:
goo_fini( g );
FAIL_F:
foo_fini( f );
FAIL:
assert( 0 != ret );
END:
return ret;
}
当然,这不像 C + + 异常 + 析构函数那样优雅。例如,以这种方式在一个函数中嵌套多个错误处理堆栈并不十分干净。相反,您可能希望将这些函数分解成类似于处理错误的自包含子函数,如下所示初始化 + 终止。
这也只能在单个函数中工作,除非高级调用方实现类似的显式错误处理逻辑,否则不会一直跳转到堆栈中,而 C + + 异常只会一直跳转到堆栈中,直到找到合适的处理程序。它也不允许您抛出任意类型,而只允许抛出错误代码。
以这种方式进行系统编码(例如,使用单个进入点和单个退出点)还可以非常容易地插入不管如何都会执行的 pre 和 post (“ finally”)逻辑。您只需将您的“ finally”逻辑放在 END 标签之后。
#include "MyOtherTricks.h"
#include "Exceptions.h"
void Testing_InnerMethod();
void Testing_PossibleExceptionThrower();
void TestExceptionHandling()
{
try
{
Testing_InnerMethod();
Say("The inner method exited without an exception.");
}
catch (Exception)
{
Say("I caught an Exception that the inner method did not catch.");
}
end_try
}
void Testing_InnerMethod()
{
try
{
Say("I am in a try block.");
Testing_PossibleExceptionThrower();
Say("The possible exception thrower didn't throw an exception.");
}
catch (ExceptionSubtype1)
Say("I caught an exception, subtype 1.");
catch (ExceptionSubtype2)
{
Say("I caught an exception, subtype 2.");
Say("I will now rethrow it.");
throw(exception);
}
end_try
}
void Testing_PossibleExceptionThrower()
{
Say("Here is the possible exception thrower.");
throw(new(ExceptionSubtype2)); // To further test exception handling, replace ExceptionSubtype2 in this line with Exception or ExceptionSubtype1, or comment out this line entirely.
Say("No, I won't throw an exception!");
}
示例代码依赖于两个文件 Exceptions.h 和 Exceptions.c:
#include <setjmp.h>
extern jmp_buf* Exception_Handler;
#define try do \
{ \
jmp_buf* outerExceptionHandler = Exception_Handler; \
jmp_buf exceptionHandler; \
Exception_Handler = &exceptionHandler; \
Exception exception = (Exception)setjmp(exceptionHandler); \
if (exception != 0) Exception_Handler = outerExceptionHandler; \
if (exception == 0) \
{ \
// The try block goes here. It must not include a return statement or anything else that exits the try...end_try block, because then the outer exception handler will not be restored.
#define catch(exceptionType) Exception_Handler = outerExceptionHandler; \
} \
else if (Object_IsSomeTypeOf(exception, exceptionType)) \
{
// The catch block goes here. It may include a return statement or anything else that exits the try...end_try block. A break statement will exit only the try...end_try block.
#define end_try } \
else \
throw(exception); \
} while(0);
void throw(Exception exception);