【问题标题】:How can I handle interrupt signal and call destructor in c++? [duplicate]如何在 C++ 中处理中断信号和调用析构函数? [复制]
【发布时间】:2012-03-25 19:41:48
【问题描述】:

可能重复:
Is destructor called if SIGINT or SIGSTP issued?

我的代码是这样的:

#include <iostream>
#include <signal.h>
#include <cstdlib>

void handler(int) {
    std::cout << "will exit..." << std::endl;
    exit(0);
}

class A {
public:
    A() {std::cout << "constructor" << std::endl;}
    ~A() {std::cout << "destructor" << std::endl;}
};

int main(void) {
    signal(SIGINT, &handler);

    A a;
    for (;;);

    return 0;
}

当我按下 Ctrl-C 时,它会打印:

constructor
^Cwill exit...

没有打印“析构函数”。 那么,如何才能干净利落地退出呢?

【问题讨论】:

  • 请注意,“正确”的答案并不像下面的一些回复所暗示的那样“微不足道”。例如,您应该考虑使用sigaction 而不是signal。此外,不在异步信号处理程序中使用exit() 的主要原因是它只是not supported。在信号处理程序中使用iostreams 也是如此(尽管我对此不是 100% 确定)。然后,使用普通的 bool 代替 volatile std::sig_atomic_t 可能不可靠或导致未定义的行为。
  • ppst.me/5cUMeA 为你写了这个小技巧,我发现这比使用全局变量来跟踪执行流程要干净得多。
  • @refp 问题是,它到处表现出未定义的行为。
  • @Christian.K 当前的实现,是的。如果您将对signal 的调用移至对handler 的调用之后(带有两个参数),则它是not 未定义的行为。写的时候有点快,自己也想到了上面的,但是有个会议要参加。
  • 如果你能负担得起特定平台的费用,请查看 gccs -fnon-call-exceptions

标签: c++ destructor signals


【解决方案1】:

困难重重。您编写的代码已经未定义 行为;不允许在信号处理程序中输出到流; 就此而言,您也不能拨打exit。 (我基于 我在这里对 Posix 标准的断言。在纯 C++ 中,你就是 允许做的是分配给sig_atomic_t类型的变量。)

在像您的代码这样的简单案例中,您可以执行以下操作:

sig_atomic_t stopFlag = 0;

void
handler( int )
{
    stopFlag = 1;
}

int
main()
{
    signal( SIGINT, &handler );
    A a;
    while ( stopFlag == 0 ) {
    }
    std::cout << "will exit..." << std::endl;
    return 0;
}

根据应用程序,您可能可以执行以下操作, 在适当的地方检查stopFlag。但一般来说,如果你 试试这个,会有竞争条件:你检查stopFlag之前 启动一个可中断的系统调用,然后进行调用;信号 在支票和通话之间到达,您拨打电话,但不是 打断了。 (我使用过这种技术,但在应用程序中 唯一可中断的系统调用是一个非常短的套接字读取 超时。)

通常,至少在 Posix 下,您最终将不得不创建一个信号 处理线程;这可以用来干净地关闭所有 其他线程。基本上,您首先将信号掩码设置为阻塞 所有信号,然后在信号处理线程中,一旦启动,将其设置为 接受您感兴趣的信号并致电sigwait()。这 但是,这意味着您执行了所有必要的常规操作 线程的干净关闭:信号处理线程必须知道 关于所有其他线程,请在它们上调用pthread_cancel,等等,你就是 编译器必须生成正确的代码来处理pthread_cancel,或者 您需要开发一些其他方法来确保所有线程都 正确通知。 (今天,人们希望所有编译器都能处理 pthread_cancel 正确。但人永远不知道;这样做有 运行时成本很高,通常不需要。)

【讨论】:

  • 特别感谢最后一段。我这样做已经有一段时间了,很高兴看到更多证据表明其他人同意这是一件好事。
  • 我不明白你对比赛条件的看法。问题只想用中断来停止程序;中断处理程序可以设置stopFlag 并且没有人会清除它。您可以在任何地方方便地测试标志(当然在任何系统调用之外)。您的中断可能会在系统调用期间或之前发生,但处理程序只是设置标志;系统调用将完成,并在下一次测试时发现标志已设置并开始关闭。您可能会损失几分之一微秒,但没有什么严重的。主要问题是确保完成定期测试。
  • 也许竞争条件不是这个场景的正确词,但问题很明显:线程可能处于长时间阻塞系统调用中(在没有人写入的套接字或管道上读取,例如)。一个信号会中断这个,但是在测试标志和发生系统调用之间有一个窗口,线程可以阻塞。
  • stopFlag 应该是 volatile
  • 如何使用setjmp/longjmp 从信号处理程序跳回主循环,然后从那里“干净”退出?这在什么条件下安全可行吗?
【解决方案2】:

你需要退出主函数的作用域才能让析构函数工作:

#include <iostream>
#include <signal.h>
#include <cstdlib>

bool stop = false;
void handler(int) {
    std::cout << "will exit..." << std::endl;
    stop = true;
}

class A {
public:
    A() {std::cout << "constructor" << std::endl;}
    ~A() {std::cout << "destructor" << std::endl;}
};

int main(void) {
  A a;
  signal(SIGINT, &handler);

  for (;!stop;);

  return 0;
}

【讨论】:

    【解决方案3】:

    这是因为正常代码和信号处理程序的上下文不同。如果您将变量 a 放在全局范围内(即在任何函数之外),您将看到析构函数被正确调用。

    如果你想自己清理(而不是让运行时和操作系统处理它),你可以有一个条件循环,像这样:

    bool keep_running = true;
    
    void handler(int) {
        std::cout << "will exit..." << std::endl;
        keep_running = false;
    }
    
    int main(void) {
        signal(SIGINT, &handler);
    
        A a;
        while (keep_running);
    
        return 0;
    }
    

    【讨论】:

    • 或者你可以。从信号处理程序调用 exit 是未定义的行为,至少根据 C90 和 Posix,因此任何事情都可能(并且实际上确实)发生。
    【解决方案4】:

    无论如何都应该释放内存。 但是如果你有要处理的代码,我想你必须跟踪你的所有对象,然后根据需要销毁它们(例如让构造函数将它们添加到std::set,而析构函数再次删除它们)。然而,这并不能确保正确的破坏顺序(这可能需要一些更复杂的解决方案)。

    您也可以使用信号处理程序设置一些标志,以离开无限循环(或您在主循环中执行的任何操作),而不是简单地使用 exit() 终止。

    【讨论】:

    • 关于第一个解决方案:您无法在信号处理程序中访问全局 std::set 而不会导致未定义的行为。 (你不能在信号处理程序中做很多事情而不引起未定义的行为。他当前的代码有未定义的行为。)
    • 嗯,是的,你是对的。所以在任何情况下都需要设置一些标志才能离开循环和/或导致额外的处理。
    • 在信号处理程序中您可以合法地做的所有事情都是设置一个标志或有效地中止程序,而不刷新 IO 缓冲区等(_exit()abort())。所以除非你想中止,你必须设置一个标志。或者在单独的线程中处理信号(至少在 Unix 下)。
    • 是的,我记得在 uni, tnx 的 Assembler 中的中断处理。 :)
    【解决方案5】:

    exit 几乎立即终止进程;特别是,具有自动存储期限的对象不会被破坏。流也被刷新和关闭,但不允许您从信号处理程序内部触摸流。所以...

    不要从信号处理程序中调用exit;设置一些原子标志来指示循环结束。

    #include <iostream>
    #include <signal.h>
    #include <cstdlib>
    
    sig_atomic_t exitRequested = 0;
    
    void handler(int) {
        std::cout << "will exit..." << std::endl;
        exitRequested = 1;
    }
    
    struct A {
         A() { std::cout << "constructor" << std::endl; }
        ~A() { std::cout << "destructor" << std::endl; }
    };
    
    int main() {
        signal(SIGINT, &handler);
    
        A a;
        for (; !exitRequested; );
    }
    

    【讨论】:

    • Nitpicker-Alert :-) 好吧,从技术上讲,exit does not 立即退出该过程 - 当然取决于您对“立即”的定义。特别是对于 C++,它确实运行静态对象的析构函数(对不起,我没有参考标准)。所有这些都是为什么 exit 不被认为是异步信号安全的,只有 _exit() 是,这与前面提到的没有任何作用。
    • @Christian.K 这不是吹毛求疵。由于信号处理程序的异步性质,您可以在信号处理程序中合法地做的事情很少。例如,在 Posix 下,您可以执行系统级别的 write,但不能使用 FILE* 或 iostream。并且exit() 是被禁止的,因为它会对FILE*iostream 进行处理(例如,刷新任何输出)。
    • @JamesKanze 哦,是的,绝对的。因此,我对原始问题的评论:-)我使用了“挑剔”一词,因为答案无论如何都废除了exit的使用(如果出于其他原因)。
    • 啊,谢谢 :-) 在 SO 上什至还有一个 related question - 去图 :-)
    • @Christian.K:不过,这是关于 _Exit,而不是 exit
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-31
    • 2013-04-17
    • 1970-01-01
    • 2010-11-08
    • 2020-04-10
    • 1970-01-01
    相关资源
    最近更新 更多