从信号处理程序中抛出异常

Evi*_*ach 22 c++ signals exception

我们有一个库,可以处理错误报告的许多方面.我的任务是将此库移植到Linux.当我的小测试套件运行时,其中一个测试失败了.测试的简化版本如下所示.

// Compiler: 4.1.1 20070105 RedHat 4.1.1-52
// Output: Terminate called after throwing an instance of 'int' abort

#include <iostream>
#include <csignal>
using namespace std;

void catch_signal(int signalNumber)
{
    signal(SIGINT, SIG_DFL);
    throw(signalNumber);
}

int test_signal()
{
    signal(SIGINT, catch_signal);

    try
    {
        raise(SIGINT);
    }
    catch (int &z)
    {
        cerr << "Caught exception: " << z << endl;
    }
    return 0;
}

int main()
{
    try
    {
        test_signal();
    }
    catch (int &z)
    {
        cerr << "Caught unexpected exception: " << z << endl;
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我的期望是将显示Caught exception:消息.实际发生的是程序终止,因为抛出的int似乎没有出现catch处理程序.

关于SO的一些问题似乎有关.我找到了许多相关的Google网页."智慧"似乎归结为.

  1. Ya不能从信号处理程序中抛出异常,因为信号处理程序使用自己的堆栈运行,因此没有定义处理程序.
  2. Ya可以从信号处理程序中抛出异常,只需在堆栈上重建一个假帧,你就可以了.
  3. 呀,我们一直这样做.它适用于我在平台X上
  4. 雅,曾经可以使用gcc,但似乎不再工作了.尝试-fnon-call-exceptions选项,也许这样可行

    代码在AIX/TRU64/MSVC编译器/环境中按预期工作.它在我们的Linux环境中失败了.


我正在寻找有助于解决此问题的建议,因此Linux上的库行为将与我的其他平台或可能实现相同功能的某种类型或解决方法相匹配.
让程序核心转储信号,不是一个可行的选择.

Cha*_*via 14

信号与C++异常完全不同.您不能使用C++ try/catch块来处理信号.具体来说,信号是POSIX概念,而不是C++语言概念.信号由内核异步传递给您的应用程序,而C++异常是由C++标准定义的同步事件.

您在POSIX信号处理程序中可以移植的内容非常有限.一种常见的策略是使用一个全局标志类型sig_atomic_t,它将在信号处理程序中设置为1,然后可能longjmp设置为适当的执行路径.

请参阅此处以获取有关编写正确信号处理程

  • 设置标志是最安全的事情.也就是说,如果你必须使用信号处理程序.信号的传递非常昂贵.内核必须构造一个特殊的堆栈帧并将各种特定于机器的上下文推送到它上面.您的信号处理程序可以在此帧中安全运行,但无法保证堆栈中存在的内容.这实际上是你无法抛出的原因......编译器无法为此上下文生成异常处理代码. (8认同)
  • 在某种程度上,安全性不是一个问题.要抛出的异常是专门以有意义的方式中止程序.无意尝试重新启动任何操作. (3认同)
  • 我猜OP知道“信号与C ++异常完全不同”。-因此,他的问题是如何将一种转化为另一种。感谢您陈述显而易见的内容。很好奇这样一个答案是否值得投票。 (3认同)
  • 好吧,那么你可能会遇到`longjmp`崩溃处理程序代码.如果此代码通常位于`catch`块中,则可以将其分解为具有`C`链接的函数. (2认同)
  • @Charles:另一个狡辩.信号不仅仅是一个POSIX概念,它们确实也出现在ISO C中(它具有较短的signal.h),并且在C++中也是如此,因为它是规范中引入的C头之一<csignal>. (2认同)
  • 这仍然不能解释为什么信号处理程序不能抛出异常。信号处理程序_is_在常规进程堆栈上执行,带有一些蹦床和调试器_can_从中提取有效的回溯。因此,应该解释开卷机为什么不能这样做。 (2认同)

Evi*_*ach 10

此代码演示了一种将异常抛出信号处理程序的技术.感谢Charles的想法.

#include <iostream>
#include <csignal>
#include <csetjmp>

using namespace std;

jmp_buf gBuffer;        // A buffer to hold info on where to jump to

void catch_signal(int signalNumber)
{
    //signal(SIGINT, SIG_DFL);          // Switch to default handling
    signal(SIGINT, catch_signal);       // Reactivate this handler.

    longjmp             // Jump back into the normal flow of the program
    (
        gBuffer,        // using this context to say where to jump to
        signalNumber    // and passing back the value of the signal.
    );
}


int test_signal()
{
    signal(SIGINT, catch_signal);

    try
    {
        int sig;
        if ((sig = setjmp(gBuffer)) == 0) 
        {
            cout << "before raise\n";
            raise(SIGINT);
            cout << "after raise\n";

        }
        else
        {
            // This path implies that a signal was thrown, and
            // that the setjmp function returned the signal
            // which puts use at this point.

            // Now that we are out of the signal handler it is
            // normally safe to throw what ever sort of exception we want.
            throw(sig);
        }
    }
    catch (int &z)
    {
        cerr << "Caught exception: " << z << endl;
    }

    return 0;
}

int main()
{
    try
    {
        test_signal();
    }
    catch (int &z)
    {
        cerr << "Caught unexpected exception: " << z << endl;
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

  • 但是,`setjmp`和`longjmp`与异常和RAII(`ctors/dtors`)不兼容.:(你可能会因此而得到资源泄漏. (8认同)

Bas*_*ard 6

我将屏蔽每个线程中的所有信号,除了一个等待信号的信号sigwait ().该线程可以无限制地处理信号,例如抛出异常或使用其他通信机制.

  • @EvilTeach,不过我同意巴斯蒂安伦纳德.在`sigwait`上有一个单独的线程等待,在处理信号时给你最大的灵活性.否则,你基本上看着使用全局标志和`longjmp`,这不是很漂亮. (3认同)