Подтвердить что ты не робот

Почему `std:: exit` не запускает деструкторы, как ожидалось?

#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);
}

Мой компилятор clang 5.0 с -std=c++1z.

Выход выглядит следующим образом:

A:1
A:2
A:4
A:5
A:3
~A:5
~A:2
~A:4
~A:1

Обратите внимание, что нет ~A:3, что означает, что объект A a3 не был разрушен.

Однако, согласно cppref:

std::exit вызывает нормальное завершение программы. Несколько шагов очистки выполняется:

Деструкторы объектов с продолжительностью локального хранения потока... гарантированно называемый.

4b9b3361

Ответ 1

Объекты с продолжительностью хранения потоков гарантированно уничтожаются только для потока, который вызывает exit. Цитирование С++ 14 (N4140), [support.start.term] 18.5/8 (выделено мной):

[[noreturn]] void exit(int status)

Функция exit() имеет дополнительное поведение в этом международном стандарте:

  • Во-первых, объекты с длительностью хранения потока и связаны с текущим потоком. Затем объекты со статической продолжительностью хранения уничтожаются и функции регистрируются путем вызова atexit называются. См. 3.6.3 для порядка разрушений и вызовов. (Автоматические объекты не уничтожен в результате вызова exit().) Если элемент управления оставляет зарегистрированную функцию, вызванную exit, потому что функция не предоставляет обработчик для исключенного броска, std::terminate() должен быть вызван (15.5.1).
  • Далее, все открытые C-потоки (опосредуемые сигнатурами функций, объявленными в <cstdio>), с неписаные буферизованные данные очищаются, все открытые потоки C закрыты, и все файлы, созданные путем вызова tmpfile() удаляются.
  • Наконец, управление возвращается в среду хоста. Если статус равен нулю или EXIT_SUCCESS, возвращается форма успешного завершения статуса. Если статус EXIT_FAILURE, возвращается форма безуспешного завершения состояния, определяемая реализацией. В противном случае возвращаемый статус определяется реализацией.

Таким образом, стандарт не гарантирует уничтожения объектов с продолжительностью хранения потоков, связанных с другими потоками, чем тот, который вызывает exit.

Ответ 2

Проблема заключается в том, что при выходе из процесса поток будет (в большинстве современных многозадачных операционных систем) принудительно убит. Это убийство потока происходит на уровне ОС, и ОС ничего не знает об объектах или деструкторах.