如果在全局对象的析构函数中调用“std::exit”会发生什么?

What should happen if one calls `std::exit` in a global object's destructor?

提问人:yeputons 提问时间:10/27/2021 最后编辑:Boannyeputons 更新时间:10/28/2021 访问量:1874

问:

请考虑以下代码:

#include <cstdlib>
struct Foo {
    ~Foo() {
        std::exit(0);
    }
} foo;
int main() {
}

它在我的 Linux(GCC、Clang)和 Windows(Visual Studio)上都成功地编译并以零成功终止。但是,当在 Windows 上使用 MSYS2 的 GCC 编译时 (),它会进入无限递归并因堆栈溢出而死亡。这可以通过在 ;我最初没有添加它,以避免考虑 .g++ (Rev2, Built by MSYS2 project) 10.3.0std::exit()std::cout

是否有任何 C++ 标准对这种行为有什么要说的?它是否定义明确/实现定义/未定义/等,为什么?

例如,最近的一些草案对 的行为说了以下几点:[support.start.term]/9.1std::exit

首先,销毁具有线程存储持续时间并与当前线程关联的对象。 接下来,销毁具有静态存储持续时间的对象,并调用通过调用 atexit 注册的函数。请参阅 [basic.start.term] 了解销毁和调用的顺序。

指的是,我猜:[basic.start.term]/1

具有静态存储持续时间的构造对象 ([dcl.init]) 将被销毁,向 std::atexit 注册的函数将作为对 std::exit ([support.start.term]) 的调用的一部分调用。 对 std::exit 的调用在销毁和注册函数之前进行排序。

我没有看到对调用析构函数的任何直接限制。std::exit

旁注:请不要评论“此代码不好”、“你不应该在全局对象中使用析构函数”(你可能不应该这样做)并在评论中探究 XY 问题。考虑这是一个来自好奇学生的学术问题,他知道他们原始问题的更好解决方案,但在探索广阔的C++草地时偶然发现了这个怪癖。

c++ language-lawyer 析构函数 终止

评论

2赞 DevSolar 10/27/2021
至少在处理堆栈时出现问题的可能性很大。它不太可能自行清理,因为它并不意味着要处理两次 - 但这种结构可能会这样做。关闭溪流也是如此......atexit()
2赞 user786653 10/27/2021
在 C 中,在附件 J.2 中,多次调用 exit 被明确称为未定义的行为(从主计数返回为调用 exit):“程序多次调用 exit 或 quick_exit 函数,或同时调用两个函数(7.22.4.4、7.22.4.7)”(是的,我知道 C 不是C++但这个限制很可能是 C++ 继承的)。
2赞 user786653 10/27/2021
结合 C++ 标准的 16.2.2 (library.c),我想你有你的答案(除非其他人能找到相反的措辞)
0赞 Nadeem Taj 10/28/2021
看起来同样的问题问了很多次(我找到了 4 个),并且只分享了一个,并希望给社区更多的空间来找到它们。Jerry Coffin Answer与这个问题完全匹配。

答:

43赞 Language Lawyer 10/27/2021 #1

[basic.start.main]/4

如果在销毁具有静态或线程存储持续时间的对象期间调用以结束程序,则该程序具有未定义的行为。std​::​exit

评论

1赞 Raildex 10/27/2021
这有点可怕,这是如何定义的
3赞 Ted Lyngmo 10/27/2021
结束程序”的部分似乎有点多余。:)
5赞 Raildex 10/28/2021
@LanguageLawyer没有。只是标准中定义了这种特殊的边缘情况
1赞 clockw0rk 10/28/2021
@Raildex是的,比如标准中没有的内容?断电时如何使程序崩溃?:D
1赞 StoryTeller - Unslander Monica 10/28/2021
@Raildex - 某些实现使用机械来调用上述析构函数。考虑到多次调用是显式 UB(特别是在 注册的函数中)这一事实,这不是一个边缘情况。它正在解决一个可能的实现。atexitexitatexit