You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

全局对象构造中调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:07:57