我有一个库(非我编写),不幸的是它在处理某些错误时使用了 abort()
。在应用程序级别上,这些错误是可恢复的,所以我想处理它们,而不是让用户看到崩溃。因此,我最终会编写如下代码:
static jmp_buf abort_buffer;
static void abort_handler(int) {
longjmp(abort_buffer, 1); // perhaps siglongjmp if available..
}
int function(int x, int y) {
struct sigaction new_sa;
struct sigaction old_sa;
sigemptyset(&new_sa.sa_mask);
new_sa.sa_handler = abort_handler;
sigaction(SIGABRT, &new_sa, &old_sa);
if(setjmp(abort_buffer)) {
sigaction(SIGABRT, &old_sa, 0);
return -1
}
// attempt to do some work here
int result = f(x, y); // may call abort!
sigaction(SIGABRT, &old_sa, 0);
return result;
}
代码不太优美。由于该模式最终需要在代码的几个位置重复使用,我希望稍微简化一下,并可能将其包装在可重用的对象中。我的第一次尝试涉及使用RAII来处理信号处理程序的设置/拆除(需要这样做是因为每个函数需要不同的错误处理)。所以我想到了这个:
template <int N>
struct signal_guard {
signal_guard(void (*f)(int)) {
sigemptyset(&new_sa.sa_mask);
new_sa.sa_handler = f;
sigaction(N, &new_sa, &old_sa);
}
~signal_guard() {
sigaction(N, &old_sa, 0);
}
private:
struct sigaction new_sa;
struct sigaction old_sa;
};
static jmp_buf abort_buffer;
static void abort_handler(int) {
longjmp(abort_buffer, 1);
}
int function(int x, int y) {
signal_guard<SIGABRT> sig_guard(abort_handler);
if(setjmp(abort_buffer)) {
return -1;
}
return f(x, y);
}
当然,这种方式使得function
的主体部分更简单、更清晰,但今天早上我想到了一些问题。这种方法是否保证可以正常工作?以下是我的想法:
- 在调用
setjmp
/longjmp
期间,没有变量是易失性或发生变化。 - 我正在
longjmp
回到与setjmp
相同的堆栈帧中的位置,并以正常方式return
,因此我允许代码执行函数出口点处编译器生成的清理代码。 - 它似乎按预期工作。
abort
调用。 - Evan Teran