为什么`std :: exit`不会按预期触发析构函数?

xml*_*lmx 24 c++ destructor global-variables thread-local-storage c++11

#include <cstdlib>
#include <thread>
#include <chrono>
#include <iostream>

using namespace std;
using namespace std::literals;

struct A
{
    int n_ = 0;
    A(int n) : n_(n) { cout << "A:" << n_ << endl; }
    ~A() { cout << "~A:" << n_ << endl; }
};

A a1(1);

int main()
{
    std::thread([]()
    {
        static A a2(2);
        thread_local A a3(3);
        std::this_thread::sleep_for(24h);
    }).detach();

    static A a4(4);
    thread_local A a5(5);

    std::this_thread::sleep_for(1s);
    std::exit(0);
}
Run Code Online (Sandbox Code Playgroud)

我的编译器是clang 5.0-std=c++1z.

输出如下:

A:1
A:2
A:4
A:5
A:3
~A:5
~A:2
~A:4
~A:1
Run Code Online (Sandbox Code Playgroud)

请注意,没有~A:3,这意味着对象A a3没有被破坏.

但是,根据cppref:

std::exit导致正常的程序终止发生.执行了几个清理步骤:

保证调用具有线程本地存储持续时间...的对象的析构函数.

Rei*_*ica 38

具有线程存储持续时间的对象保证仅针对调用的线程销毁exit.引用C++ 14(N4140),[support.start.term] 18.5/8(强调我的):

[[noreturn]] void exit(int status)
Run Code Online (Sandbox Code Playgroud)

该功能exit()在本国际标准中有其他行为:

  • 首先,销毁具有线程存储持续时间并与当前线程相关联的对象.接下来,销毁具有静态存储持续时间的对象,并调用通过调用atexit注册的函数.有关破坏和调用的顺序,请参见3.6.3.(自动对象不会因调用而被销毁exit().)如果控件离开了一个被调用的注册函数,exit因为该函数没有为抛出异常提供处理程序,std::terminate()则应调用(15.5.1).
  • 接下来,<cstdio>刷新所有打开的C流(由声明的函数签名调解)和未写入的缓冲数据,关闭所有打开的C流,并tmpfile()删除通过调用创建的所有文件 .
  • 最后,控制权返回给主机环境.如果status为零EXIT_SUCCESS,则返回状态成功终止的实现定义形式.如果status为EXIT_FAILURE,则返回状态为不成功终止的实现定义形式.否则返回的状态是实现定义的.

因此,该标准不保证具有与其他线程相关联的线程存储持续时间的对象的销毁exit.

  • 这表明cppreference(引用的问题)与C++规范不一致; 它应该被报告为该网站上的错误. (10认同)
  • @HansOlsson:cppreference.com是一个wiki,所以如果你想要的话,你可以自己修复它,而不是将其报告为bug.:-) (5认同)

Som*_*ude 15

这里的问题是当你退出进程时,线程将(在大多数现代多任务操作系统上)被强行杀死.这种线程的查杀发生在操作系统级别,操作系统对对象或析构函数一无所知.

  • 对不起,我不认为这回答了这个问题.`std :: exit`在退出进程之前做了一堆清理,这就是OP要求的清理.在流程退出之后,没有人期望析构函数被调用*. (2认同)
  • std::exit 不是操作系统级别的函数或系统调用。它是一个 C++ 标准库函数,因此它会了解 C++ 语言结构。操作系统是否了解对象或者操作系统是否正在运行都无关紧要。 (2认同)