longjmp和RAII

Eva*_*ran 10 c++ raii signal-handling longjmp

所以我有一个库(不是我写的),不幸的是它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;
}
Run Code Online (Sandbox Code Playgroud)

代码不是很优雅.由于这种模式最终必须在代码的几个点重复,我想简化一点,并可能将它包装在一个可重用的对象中.我的第一次尝试涉及使用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);
}
Run Code Online (Sandbox Code Playgroud)

当然,身体function很多更简单,更清晰的这种方式,但今天上午一个念头出现在我.这保证有效吗?这是我的想法:

  1. 没有变量是易变的,或者在调用setjmp/ 之间没有变化longjmp.
  2. 我正常和正常的longjmp堆栈帧中的位置,所以我允许代码执行编译器在函数的出口点发出的清理代码.setjmpreturn
  3. 它似乎按预期工作.

但我仍然觉得这可能是未定义的行为.你们有什么感想?

Mar*_*k B 8

我认为这f是在第三方库/应用程序,因为否则你可以修复它不要调用中止.鉴于此,并且RAII可能会或可能不会在所有平台/编译器上可靠地产生正确的结果,您有几个选择.

  • 创建一个小的共享对象,定义abort和LD_PRELOAD它.然后你控制在中止时发生的事情,而不是在信号处理程序中.
  • f在子进程中运行.然后,您只需检查返回代码,如果失败,请尝试使用更新的输入.
  • 不要使用RAII,只需function从多个呼叫点呼叫您的原始设备,然后让它手动执行设置/拆卸.在这种情况下,它仍然会消除复制粘贴.