Почему `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 вызывает нормальное завершение программы. Выполняется несколько этапов очистки:

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


person xmllmx    schedule 28.03.2017    source источник
comment
Потому что вы «отсоединяете» его, я полагаю.   -  person SingerOfTheFall    schedule 28.03.2017
comment
Является ли это дубликатом stackoverflow.com/questions/19744250/?   -  person François Andrieux    schedule 28.03.2017
comment
Нет. То же самое, даже если нить не отсоединена.   -  person xmllmx    schedule 28.03.2017
comment
Нет, это не так: ideone.com/ZpNcTm   -  person SingerOfTheFall    schedule 28.03.2017
comment
@SingerOfTheFall, вы изменили код и изменили семантику. Поток должен быть активен при вызове exit(0).   -  person xmllmx    schedule 28.03.2017
comment
@Франсуа Андриё. Сообщение, которое вы процитировали, не совпадает с моим вопросом.   -  person xmllmx    schedule 28.03.2017
comment
В качестве побочного комментария, выход из процесса без вызова деструкторов является желательным поведением, потому что нет смысла освобождать память, которая будет освобождена (быстрее) ОС сразу после exit() (и IO должен быть очищен перед его вызовом).   -  person Synxis    schedule 28.03.2017
comment
@Synxis Это, конечно, предполагает, что все деструкторы просто выполняют простое управление памятью и не делают ничего более сложного. Выход без вызова деструкторов явным образом нарушает гарантию очистки std::basic_fstream (например), потому что поведение очистки при уничтожении вызвано деструктором члена std::basic_filebuf.   -  person Justin Time - Reinstate Monica    schedule 28.03.2017
comment
В общем, нет никакого способа чисто убить поток извне. Ваш код (или библиотека, или среда выполнения компилятора) должен вызываться самим потоком (включая вызов самого потока как последнего, что он делает). IOW, вы должны кодировать логику выхода из потока для каждого потока.   -  person hyde    schedule 29.03.2017
comment
@JustinTime Я согласен (поэтому я сказал, что ввод-вывод следует очистить перед вызовом exit).   -  person Synxis    schedule 29.03.2017
comment
@Synxis Я хочу сказать, что, учитывая, что std::exit() предназначен для нормального завершения программы (включая вызов кода очистки), пользователи должны иметь возможность безоговорочно предположить, что вызов std::exit имеет тот же результат, что и выход из области main(), которая включает вызов всех ожидаемых деструкторов. Если exit() не сможет вызвать деструкторы, которые, как ожидается, будут вызываться при выходе, это нарушит значительную часть кода, включая, например, все, что полагается на гарантию basic_fstream, что он автоматически сам себя очищает при уничтожении. , без явного указания сделать это.   -  person Justin Time - Reinstate Monica    schedule 30.03.2017
comment
@JustinTime Когда процесс завершается, вы должны сделать только две вещи: очистить файловые буферы и уведомить подключенные программы (если это требуется протоколу). Все остальное, от управления оперативной памятью до закрытия файловых дескрипторов, следует оставить на усмотрение ОС. Для этого нам нужна функция, и exit() сделал эту работу. Возможно, в будущем стандарт изменится, и exit() будет работать так, как вы сказали, а quick_exit() возьмет на себя предыдущую работу exit(). См. blogs.msdn.microsoft.com/oldnewthing/20120105-00. /?p=8683. Конечно, вы не должны вызывать exit() в отладке, чтобы вызывались все ваши деструкторы.   -  person Synxis    schedule 30.03.2017
comment
@Synxis Я надеюсь, что так и будет, хотя, вероятно, пройдет еще как минимум несколько лет, прежде чем quick_exit() будет использоваться достаточно часто для изменения такой величины. Тем не менее, он имеет такую ​​​​же гарантию в отношении файлового ввода-вывода, что является определенным преимуществом.   -  person Justin Time - Reinstate Monica    schedule 31.03.2017


Ответы (2)


Объекты с продолжительностью хранения потока гарантированно будут уничтожены только для потока, который вызывает 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.

person Angew is no longer proud of SO    schedule 28.03.2017
comment
Это указывает на то, что cppreference (упомянутый вопрос) не согласуется со спецификацией C++; и об этом следует сообщать как об ошибке на этом веб-сайте. - person Hans Olsson; 28.03.2017
comment
@HansOlsson: cppreference.com — это вики, поэтому вместо того, чтобы сообщать об этом как об ошибке, вы можете исправить это самостоятельно, если хотите. :-) - person ruakh; 29.03.2017

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

person Some programmer dude    schedule 28.03.2017
comment
Извините, но я не думаю, что это вообще отвечает на вопрос. std::exit выполняет кучу очистки перед выходом из процесса, и это та очистка, о которой спрашивает OP. Никто не ожидал, что деструкторы будут вызываться после завершения процесса. - person ruakh; 31.03.2017
comment
std::exit не является функцией уровня ОС или системным вызовом. Это стандартная библиотечная функция C++, поэтому она будет знать о языковых конструкциях C++. Знает ли ОС об объектах и ​​работает ли ОС вообще, не имеет значения. - person josefx; 26.04.2017