std::quick_exit和std::abort有什么区别?为什么需要std::quick_exit?

51
C++11引入了一种新的程序执行结束的方式——std::quick_exit
引用N3242 18.5(第461页)的话:
[[noreturn]] void quick_exit(int status) noexcept;
效果:调用at_quick_exit注册的函数按照它们注册的相反顺序被调用,但是一个函数将在任何已经被调用的先前注册函数之后被调用。作为调用quick_exit的结果,对象不应被销毁。如果通过quick_exit调用的已注册函数因未为抛出的异常提供处理程序而导致控制离开,则应调用terminate()。注意:at_quick_exit可能会从不同于注册它的线程调用已注册的函数,因此已注册的函数不应依赖于具有线程存储期的对象的身份。调用注册函数后,quick_exit将调用_Exit(status)。注意:标准文件缓冲区未被刷新。请参见:ISO C 7.20.4.4。
std::abort(void)std::_Exit(int status)的定义仅在能否将状态传递给父进程方面有所不同,这引发了我的问题。
这是否意味着std::quick_exitstd::abort之间语义上唯一的区别是std::quick_exit调用使用std::at_quick_exit注册的函数并允许设置返回状态?
引入此函数的原因是什么?
3个回答

54

这里有一篇很好的文章可供阅读,我将简要概括一下。该功能是专门为处理使用线程时结束程序困难而添加的。由于退出事件通常是高度异步的,如用户关闭用户界面,管理员关闭机器等,这发生时并不考虑程序启动的线程状态,它们几乎总处于高度不可预测的状态。

在理想的情况下,程序的main()函数请求线程退出,通常通过信号事件,等待线程结束,然后通过exit()退出main()以进行干净的关闭。然而,实现这一理想非常困难。一个线程可能深深地嵌入到系统调用中,比如等待某个I/O完成。或者它在一个需要按正确顺序由另一个线程发出信号的同步对象上阻塞。结果很少令人愉快,真正的程序经常在退出时死锁。或者在关机顺序不符合预期时崩溃。

针对这个问题还有一个简单且非常诱人的解决方法:直接调用_exit()。程序就结束了,操作系统清除留下的破片。但很明显没有进行任何清理,这有时会很混乱,留下像半写的文件或不完整的数据库事务等痕迹。

std::quick_exit()提供了一种替代方法。类似于_exit()但仍具有执行某些代码的选项,这些代码是在at_quick_exit注册的。


15
另外,由于abort信号会触发SIGABRT,因此调用abort通常会在*nix系统上导致核心转储(core dump),在Windows系统上会弹出一个窗口(例如“程序已停止工作,关闭/调试”)。仅在出现意外状况并且您想要生成核心转储/迷你转储来诊断原因时才使用abort - vladr
3
我一直希望我的眼睛能够在两句话中明确表达出什么是真正重要的,使用了大量的反引号和加粗字体。但不,我需要一双新的眼睛。如果有购物推荐就更好了。 - Hans Passant

30
讨论了 N1327N2440,阐述了使用 std::quick_exit 的原因。 quick_exit_Exitexit 之间的主要区别在于如何处理静态析构函数并将关键信息刷新到稳定存储器中:
  • std::_Exit:不执行静态析构函数或刷新关键 I/O。
  • std::exit:执行静态析构函数并刷新关键 I/O。
  • std::quick_exit:不执行静态析构函数,但刷新关键 I/O。

(正如提到的,std::abort 只发送 SIGABRT。)


7
我在标准中看不到任何证据表明std::quick_exit会刷新关键IO。实际上,标准指出:“注意:标准文件缓冲区不会被刷新。”那么您认为std::quick_exit刷新关键的IO的依据是什么,而且,您所说的“关键”IO是什么意思? - KnowItAllWannabe
7
他将critical定义为“你足够关心它以注册一个at_quick_exit处理程序”。 - Ben Voigt

4

std::abort会在不调用任何使用"at_exit/at_quick_exit"注册的函数的情况下终止应用程序。而另一方面,std::quick_exit会像你指出的那样,调用使用std::at_quick_exit注册的函数。

std::abort通常会使应用程序异常终止,这应该在发生异常情况并且需要在不进行任何清理的情况下关闭应用程序时调用。从std::abort文档中可以得知:

除非被传递到信号处理程序并且处理程序不返回,否则会导致异常程序终止,除非SIGABRT被捕获。

当您想要执行一些清理操作时,std::quick_exit将更为适当。由于它最终调用std::_Exit而不是像std::abort一样发出一个信号(它发出SIGABRT信号,使应用程序异常停止),因此这个函数也允许您以优雅的方式停止应用程序。

std::exit允许您平稳退出应用程序,同时仍然清理自动、线程局部和静态变量。而std::quick_exit则不会进行清理。这就是为什么它的名称中有一个“quick_”,它更快,因为它跳过了清理阶段。

因此,两个函数之间存在实际的语义差异。一个以异常方式停止应用程序,而另一个执行平稳退出,允许您进行一些清理操作。


1
这仍然没有解决 - 当“优雅”地终止时,为什么允许进行一些清理而不是全部清理?我从未在实际代码中看到过quick_exit,但如果我在代码审查中遇到它,我可能会想要求进行完整的清理或不进行任何清理。是否有关于此的WG21来源? - Rafał Rawicki
如果您想要完全清理,那么您应该使用 "exit"。quick_exit 更快,因为它不调用任何自动、线程本地和静态变量的析构函数。我认为最好比较 std::quick_exit 和 std::exit,因为它们都允许用户执行优雅的关闭,而 std::abort 基本上会异常终止您的应用程序。 - mfontanini
6
C++的析构函数通常会做一些在进程退出时不必要的事情(比如释放内存,因为操作系统会负责)。我猜quick_exit可以让你进行一些清理工作,但是其余的清理工作则由操作系统来处理。 - Roger Lipscombe
1
std::exit 对于具有自动持续期的变量不起作用。对于在 main 中最终被捕获的异常的堆栈展开更为适当。 - Luc Danton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接