全局对象构造中调用std::atexit的执行顺序可靠性问询
关于
std::atexit注册函数与静态对象销毁顺序的问题 首先引用cppreference对std::atexit的说明:
注册的函数可能与静态存储期对象的销毁、以及彼此之间并发调用,但保证若A的注册先于B的注册,则B的调用先于A的调用;该顺序规则同样适用于静态对象构造函数与atexit调用之间的序列关系。
先纠正你对“may be called concurrently”的理解:这里的并发是指多线程语境下的并发执行——标准允许退出阶段的销毁/atexit函数调用在多线程中执行,不过实际程序退出时通常是单线程处理销毁逻辑,所以这种场景比较少见。
回到你的核心疑问:在顺序规则的语境中,静态对象是在构造开始还是完成时被视为“初始化完成”?以及你测试得到的行为是否可依赖?
标准中的明确规则
根据C++标准的[basic.start.term]章节:
- 静态存储期对象的销毁顺序,与其完成初始化的顺序相反(当初始化顺序可确定时)。
- 当你在静态对象的构造函数内调用
std::atexit时,这个注册操作的序列点是在该对象构造完成之前——也就是说,注册函数的注册顺序早于该对象初始化完成的顺序。
结合std::atexit的核心规则:注册越早的函数,退出时被调用越晚,你注册的lambda函数会在my_foo销毁之后执行,这完全符合标准规定。
测试代码与输出验证
你的测试代码:
#include <cstdlib> #include <iostream> struct foo { foo() { std::cout << "ctor\n"; std::atexit([]() { std::cout << "atexit\n"; }); } ~foo() { std::cout << "dtor\n"; } }; foo my_foo; int main() { return 0; }
输出结果:
ctor dtor atexit
这个行为是完全符合标准、可以依赖的,既不是实现定义行为,也不是未定义行为。
补充注意点
- 仅当静态对象的初始化顺序可确定时(比如同一个翻译单元内的静态对象),销毁顺序和
std::atexit的交互才是严格可预测的。 - 跨翻译单元的静态对象初始化顺序是未指定的,但一旦初始化顺序确定,销毁顺序就会与之相反,
std::atexit的注册顺序也会遵循同样的反向规则。
内容的提问来源于stack exchange,提问作者François Andrieux
相关产品推荐
相关产品推荐

