这种情况下是保证订单还是UB?
#include <iostream>
#include <cassert>
using namespace std;
struct TraceHelper
{
TraceHelper()
{
cout << "TraceHelper::constructor()" << endl;
}
~TraceHelper()
{
cout << "TraceHelper::destructor()" << endl;
}
};
void trace_fn()
{
static TraceHelper th;
cout << "trace_fn()" << endl;
}
void my_atexit()
{
cout << "my_atexit()" << endl;
}
int main()
{
cout << "Entered main()" << endl;
assert(0 == atexit(my_atexit));
trace_fn();
trace_fn();
cout << "Exiting main()" << endl;
return 0;
}
输出:
Entered main()
TraceHelper::constructor()
trace_fn()
trace_fn()
Exiting main()
TraceHelper::destructor()
my_atexit()
我们这里看到的是main函数和std::exit:
的交互从main函数返回,[...]执行正常函数 终止(自动调用变量的析构函数 存储持续时间),然后执行 std::exit,传递参数 返回语句的(如果使用隐式返回则为 0 )作为 退出代码。
就静态对象的破坏而言,与问题相关的是以下几行:
如果完成静态对象A的初始化是 在为某些函数 F 调用 std::atexit 之前排序,调用 终止期间到 F 在开始之前排序 破坏 A.
如果对某个函数 F 的 std::atexit 调用是 sequenced-before 完成静态对象的初始化 A,A 的销毁开始顺序在对 F 的调用之前 在终止期间。
在问题的代码中,函数
my_atexit()
是在函数std::atexit()
中创建静态对象th
之前使用trace_fn()
注册的。因此,TraceHelper
的析构函数调用发生在调用my_atexit()
之前。行为符合预期。
在您的 specific 案例中(即 local 静态对象的案例),cppreference 有这样的说法(在“自 C++11 起”部分):
- 具有线程本地存储持续时间的对象的析构函数 与当前线程相关联,对象的析构函数 具有静态存储持续时间,以及注册的功能
并发执行,同时保持 以下保证:std::atexit
…
c) 如果完成 静态对象 A 的初始化在调用之前排序对于某些函数 F,终止期间对 F 的调用是 在 Astd::atexit
的销毁开始之前排序 d) 如果调用 到对于某些函数 F 在 静态对象A初始化完成,开始 A 的销毁在终止期间对 F 的调用之前进行排序。std::atexit
因此,由于
th
已初始化 – 根据定义 – 第一次执行通过它的声明,那么您对 atexit
的调用将在第一次调用 trace_fn()
和随后的 tf
构造之前完全排序
.因此,上述引文中的“d”段成立,您看到的销毁/终止序列是明确定义的。
这是C++17标准草案的“等效”部分:
6.8.3.4 终止 [basic.start.term]
…
5 如果 使用静态存储完成对象的初始化 duration 强烈发生在调用之前,调用 传递给std::atexit
的函数在调用之前排序 对象的析构函数。如果强烈呼唤std::atexit
发生在对象初始化完成之前 静态存储持续时间,调用对象的析构函数是 在调用传递给std::atexit
的函数之前排序。 …std::atexit
(这里是最新的在线 C++ 标准草案的等效部分。)
还要注意,这种块作用域
static
对象的析构函数和对已注册 atexit
处理程序的调用被称为“并发”(请参阅有关可能滥用该术语的评论),并提供保证,因此您可以拥有对多个析构函数的调用与对多个退出处理程序的调用“混合”在一起。