Is it legal to initialize a thread_local variable in the destructor of a global variable? Is it legal to initialize a thread_local variable in the destructor of a global variable? multithreading multithreading

Is it legal to initialize a thread_local variable in the destructor of a global variable?


The Standard does not cover this case; the strictest reading would be that it is legal to initialize a thread_local in the destructor of an object with static storage duration, but it is illegal to allow the program to continue to normal completion.

The problem arises in [basic.start.term]:

1 - Destructors ([class.dtor]) for initialized objects (that is, objects whose lifetime ([basic.life]) has begun) with static storage duration are called as a result of returning from main and as a result of calling std::exit ([support.start.term]). Destructors for initialized objects with thread storage duration within a given thread are called as a result of returning from the initial function of that thread and as a result of that thread calling std::exit. The completions of the destructors for all initialized objects with thread storage duration within that thread are sequenced before the initiation of the destructors of any object with static storage duration. [...]

So the completion of bar::~Bar::foo::~Foo is sequenced before the initiation of bar::~Bar, which is a contradiction.

The only get-out could be to argue that [basic.start.term]/1 only applies to objects whose lifetime has begun at the point of program/thread termination, but contra [stmt.dcl] has:

5 - The destructor for a block-scope object with static or thread storage duration will be executed if and only if it was constructed. [ Note: [basic.start.term] describes the order in which block-scope objects with static and thread storage duration are destroyed. — end note ]

This is clearly intended to apply only to normal thread and program termination, by return from main or from a thread function, or by calling std::exit.

Also, [basic.stc.thread] has:

A variable with thread storage duration shall be initialized before its first odr-use ([basic.def.odr]) and, if constructed, shall be destroyed on thread exit.

The "shall" here is an instruction to the implementor, not to the user.

Note that there is nothing wrong with beginning the lifetime of the destructor-scoped thread_local, since [basic.start.term]/2 does not apply (it is not previously destroyed). That is why I believe that undefined behavior occurs when you allow the program to continue to normal completion.

Similar questions have been asked before, though about static vs. static storage duration rather than thread_local vs. static; Destruction of objects with static storage duration (and https://groups.google.com/forum/#!topic/comp.std.c++/Tunyu2IJ6w0), and Destructor of a static object constructed within the destructor of another static object. I'm inclined to agree with James Kanze on the latter question that [defns.undefined] applies here, and the behavior is undefined because the Standard does not define it. The best way forward would be for someone with standing to open a defect report (covering all the combinations of statics and thread_locals initialized within the destructors of statics and thread_locals), to hope for a definitive answer.


Write your program as

#include <iostream>thread_local struct Foo {    Foo() { std::cout << "Foo()\n"; }    ~Foo() { std::cout << "~Foo()\n"; }} t;struct Bar {    Bar() { std::cout << "Bar()\n"; }    ~Bar() { std::cout << "~Bar()\n"; t; }} b;int main() {    return 0;}

If Foo wasn't thread_local, then Foo t and Bar b are on equal position, and it's possible to destruct Foo t before Bar b.

In this case, when referring to t in b.~Bar(), it's referring to a destructed struct, which IMO should be a UB(on some system destructing struct free its memory).

Therefore, adding thread_local it's still undefined behavior