【问题标题】:longjmp and RAIIlongjmp 和 RAII
【发布时间】:2011-07-20 14:44:49
【问题描述】:

所以我有一个库(不是我写的),不幸的是它使用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 的主体更加这样更简单、更清晰,但今天早上我想到了。 这能保证有效吗?这是我的想法:

  1. 在调用setjmp/longjmp 之间没有变量是易失的或变化的。
  2. longjmping 到与setjmpreturning 通常位于同一堆栈帧中的位置,因此我允许代码执行编译器在退出点发出的清理代码功能。
  3. 它似乎按预期工作。

但我仍然觉得这可能是未定义的行为。大家觉得呢?

【问题讨论】:

  • 为什么不使用 C++ 异常?它可以轻松解决您的问题。
  • @Hans:c++ 异常不会捕获abort 调用。
  • 把它们扔到你的 abort_handler() 中,这没什么不同。
  • @Hans 在信号处理程序中抛出异常并不能保证在所有平台上都能正常工作,因此本质上是不可移植的。

标签: c++ raii signal-handling setjmp


【解决方案1】:

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

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

【讨论】:

  • +1,我正在考虑在我的回答中添加插入或线程/叉作为建议
【解决方案2】:

我真的很喜欢你的解决方案,并且在测试工具中编写了类似的代码来检查目标函数 assert()s 是否符合预期。

我看不出有任何理由让这段代码调用未定义的行为。 C 标准似乎祝福了它:abort() 产生的处理程序不受从处理程序调用库函数的限制。 (注意:这是 C99 的 7.14.1.1(5) - 遗憾的是,我没有 C90 的副本,即 C++ 标准引用的版本)。

C++03 增加了一个进一步的限制:如果任何自动对象将被抛出的异常破坏,将控制权转移到程序中的另一个(目标)点,则调用 longjmp(jbuf, val)将控制权转移到同一(目标)点的抛出点具有未定义的行为。我假设您的声明“没有变量是易失的或在对 setjmp/longjmp 的调用之间发生变化”包括实例化任何自动 C++ 对象。 (我猜这是一些遗留的 C 库?)。

POSIX 异步信号安全(或缺乏安全)也不是问题 - abort() 与程序执行同步生成其 SIGABRT。

最大的担忧是破坏 3rd 方代码的全局状态:作者不太可能在abort() 之前努力使状态保持一致。但是,如果您是正确的,没有变量发生变化,那么这不是问题。

如果对标准语言有更好理解的人可以证明我错了,我将不胜感激。

【讨论】:

  • 我相当肯定变量没有改变。这是一个 C 库,只有当它被要求执行一个需要它到malloc 与不透明类型的初始化相关的大量空间的操作时才会中止)。如果失败,那么我可以只考虑未初始化的 opaque 类型,并尝试创建一个具有更合理值的新类型。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多