在全局变量的析构函数中初始化一个 thread_local 变量是否合法?

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

这个节目:

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

版画

Bar()
~Bar()
Foo()

对我来说(GCC 6.1,Linux,x86-64)。 ~Foo() 永远不会被调用。这是预期的行为吗?

标准不包括这种情况;最严格的解读是,在静态存储持续时间的对象的析构函数中初始化一个thread_local是合法的,但是让程序继续正常完成是非法的

问题出现在[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. [...]

所以bar::~Bar::foo::~Foo的完成顺序在bar::~Bar的开始之前,这是矛盾的。

唯一的出路可能是争论 [basic.start.term]/1 仅适用于生命周期从 program/thread 终止点开始的对象,但 相反 [stmt.dcl] 有:

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 ]

这显然仅适用于正常的线程和程序终止,通过 return 来自 main 或线程函数,或通过调用 std::exit.

另外,[basic.stc.thread] 有:

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.

这里的"shall"是给实现者的指令,不是给用户的。

请注意,开始析构函数作用域 thread_local 的生命周期没有任何问题,因为 [basic.start.term]/2 不适用(它之前未被销毁)。这就是为什么我认为当您允许程序继续正常完成时会发生未定义的行为。

之前已经问过类似的问题,不过是关于静态与静态存储持续时间而不是 thread_local 与静态; 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] 适用于此处,行为未定义,因为标准未对其进行定义。最好的方法是让有资格的人打开缺陷报告(涵盖在 static 和 [=10= 的析构函数中初始化的 staticthread_local 的所有组合]s),希望得到明确的答案。

把你的程序写成

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

如果Foo不是thread_local,那么Foo tBar b处于相同的位置,并且可以在[=之前破坏Foo t 13=]。

在这种情况下,当在 b.~Bar() 中引用 t 时,它指的是一个被破坏的结构,IMO 应该是一个 UB(在某些系统上破坏结构释放它的内存)。

因此,添加thread_local它仍然是未定义的行为