Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

This program:

#include <iostream>

struct Foo {
    Foo() {
        std::cout << "Foo()\n";
    }

    ~Foo() {
        std::cout << "~Foo()\n";
    }
};

struct Bar {
    Bar() {
        std::cout << "Bar()\n";
    }

    ~Bar() {
        std::cout << "~Bar()\n";
        thread_local Foo foo;
    }
};

Bar bar;

int main() {
    return 0;
}

Prints

Bar()
~Bar()
Foo()

for me (GCC 6.1, Linux, x86-64). ~Foo() is never called. Is that the expected behaviour?

like image 794
Tavian Barnes Avatar asked Jun 30 '16 17:06

Tavian Barnes


People also ask

Why use thread_ local?

thread_local is used to mark a variable with thread storage duration, which means it is created when the thread starts and cleaned up when the thread ends. Note: For C++, C++11 or later is required to use the thread_local keyword.

What does thread_ local do in c++?

In C++, thread_local is defined as a specifier to define the thread-local data and this data is created when the thread is created and destroyed when the thread is also destroyed, hence this thread-local data is known as thread-local storage.

Does thread_ local imply static?

If thread_local is the only storage class specifier applied to a block scope variable, static is also implied.


1 Answers

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.

like image 112
ecatmur Avatar answered Oct 02 '22 16:10

ecatmur