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

My compiler is clang 5.0 with -std=c++1z.

The output is as follows:

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

Note that there is no ~A:3, which means the object A a3 was not destructed.

However, according to cppref:

std::exit causes normal program termination to occur. Several cleanup steps are performed:

The destructors of objects with thread local storage duration ... are guaranteed to be called.

200_success
  • 7,286
  • 1
  • 43
  • 74
xmllmx
  • 39,765
  • 26
  • 162
  • 323
  • Because you 'detach` it, I suppose. – SingerOfTheFall Mar 28 '17 at 14:06
  • 1
    Is this a duplicate of http://stackoverflow.com/questions/19744250/what-happens-to-a-detached-thread-when-main-exits? – François Andrieux Mar 28 '17 at 14:07
  • 1
    No. It's same even if the thread is not detached. – xmllmx Mar 28 '17 at 14:07
  • No it's not: http://ideone.com/ZpNcTm – SingerOfTheFall Mar 28 '17 at 14:08
  • @SingerOfTheFall, You modified the code and changed the semantics. The thread must be alive when calling `exit(0)`. – xmllmx Mar 28 '17 at 14:10
  • @FrançoisAndrieux. The post you cited is not the same question as mine. – xmllmx Mar 28 '17 at 14:10
  • As a side comment, exiting a process without calling destructors is a wanted behavior, because there is no point freeing memory that will be freed (faster) by the OS immediately after the `exit()` (and IO should be cleaned before calling it). – Synxis Mar 28 '17 at 19:01
  • 4
    @Synxis That, of course, assumes that all destructors merely perform simple memory management, and don't do anything more complex. Exiting without calling destructors explicitly breaks `std::basic_fstream`'s flushing guarantee (for example), though, because the flush-on-destruction behaviour is caused by its member `std::basic_filebuf`'s destructor. – Justin Time - Reinstate Monica Mar 28 '17 at 20:47
  • 1
    In general, there is no way to kill a thread from outside cleanly. Your (or library, or compiler runtime) code must be called by the thread itself (including calling thread exit itself as last thing it does). IOW, you have to code thread exit logic to every thread. – hyde Mar 29 '17 at 03:52
  • @JustinTime I agree (that's why I said that IO should be cleaned before calling `exit`). – Synxis Mar 29 '17 at 09:19
  • @Synxis My point is that, considering that `std::exit()` is intended for normal program termination (which includes calling cleanup code), users should be able to unconditionally assume that calling `std::exit` has the same result as exiting `main()`'s scope, which includes calling all expected destructors. If `exit()` failed to call the destructors that exiting is expected to call, it would break a significant amount of code, including, e.g., everything that relies on `basic_fstream`'s guarantee that it'll automatically clean _itself_ up at destruction, without explicitly being told to do so. – Justin Time - Reinstate Monica Mar 29 '17 at 21:12
  • @JustinTime When the process is exiting, there are only two things you should do: flush the file buffers and notify connected programs (if the protocol needs it). Everything else, from RAM management to closing file handles, should be left to the OS. We need a function for that, and `exit()` did that job. Maybe in the future the standard could change so that `exit()` works as you said and `quick_exit()` take on `exit()`'s previous job. See https://blogs.msdn.microsoft.com/oldnewthing/20120105-00/?p=8683/. Of course you should not call `exit()` in debug, so that all your destructors are called. – Synxis Mar 30 '17 at 09:27
  • @Synxis I hope it will, although it'll probably take at least a few more years before `quick_exit()` is used commonly enough for a change of that magnitude. It has the same guarantee regarding file I/O, though, which is a definite point in its favour. – Justin Time - Reinstate Monica Mar 31 '17 at 00:26

2 Answers2

39

Objects with thread storage duration are guaranteed to be destroyed only for the thread which calls exit. Quoting C++14 (N4140), [support.start.term] 18.5/8 (emphasis mine):

[[noreturn]] void exit(int status)

The function exit() has additional behavior in this International Standard:

  • First, objects with thread storage duration and associated with the current thread are destroyed. Next, objects with static storage duration are destroyed and functions registered by calling atexit are called. See 3.6.3 for the order of destructions and calls. (Automatic objects are not destroyed as a result of calling exit().) If control leaves a registered function called by exit because the function does not provide a handler for a thrown exception, std::terminate() shall be called (15.5.1).
  • Next, all open C streams (as mediated by the function signatures declared in <cstdio>) with unwritten buffered data are flushed, all open C streams are closed, and all files created by calling tmpfile() are removed.
  • Finally, control is returned to the host environment. If status is zero or EXIT_SUCCESS, an implementation-defined form of the status successful termination is returned. If status is EXIT_FAILURE, an implementation-defined form of the status unsuccessful termination is returned. Otherwise the status returned is implementation-defined.

The standard therefore does not guarantee destruction of objects with thread storage duration associated with other threads than the one calling exit.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • 10
    That indicates that cppreference (quoted in question) is not consistent with the C++-specification; and it should be reported as a bug on that web-site. – Hans Olsson Mar 28 '17 at 14:39
  • 5
    @HansOlsson: cppreference.com is a wiki, so rather than reporting this as a bug, you can fix it yourself if you want. :-) – ruakh Mar 29 '17 at 03:36
16

The problem here is that when you exit the process, the thread will be (on most modern multi-tasking operating systems) forcibly killed. This killing of the thread happens at the OS level, and the OS doesn't know anything about objects or destructors.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • 3
    I'm sorry, but I don't think this answers the question at all. `std::exit` does a bunch of cleanup before exiting the process, and that is the cleanup the OP is asking about. No one was expecting destructors to be called *after* the process exited. – ruakh Mar 31 '17 at 04:34
  • 2
    std::exit is not an OS level function or system call. It is a C++ standard library function, as such it would be aware of C++ language constructs. Whether the OS knows about objects or if there is an OS running at all is irrelevant. – josefx Apr 26 '17 at 10:15