先看代码#include iostream #include memory class Widget { public: Widget() { std::cout constructor ; } ~Widget() { std::cout destructor ; } }; std::unique_ptrWidget widget std::make_uniqueWidget(); int main() { std::cout main ; return 0; }这段代码会输出什么先说答案会输出constructor main destructor为什么呢一、初始化分两个阶段不是一次完成C 标准[basic.start.static]/[basic.start.dynamic]把全局/静态对象的初始化分为阶段时机包含内容静态初始化程序启动最早期main 之前很久零初始化、常量初始化如int x 42;、constexpr对象动态初始化main 之前但可能延迟到首次使用需要运行代码的初始化如调用构造函数、int x f();举例int a 0; int b 42; int c some_func(); Widget w; 二、构造顺序同一文件内确定跨文件未定义cpp// file_a.cppA globalA;// file_b.cppB globalB;globalA和globalB谁先构造是未定义的——这就是著名的 Static Initialization Order Fiasco (SIOF)。如果B的构造函数依赖A已经构造好就会踩坑。解决方案是 Construct On First Use 惯用法用函数内的static变量A getGlobalA() {static A instance;return instance;}三、释放不是简单的main 之后更准确的说法是全局对象的析构函数在main正常返回之后或调用std::exit()时执行。注意三种不会调用析构函数的退出方式退出方式是否调析构return 0;from main✅ 会std::exit(0)✅ 会std::abort()❌ 不会std::_Exit()/_exit()❌ 不会std::quick_exit()❌ 不会只调at_quick_exit注册的函数未捕获异常逃出 main⚠️ 实现定义通常是 abort不调析构析构顺序与构造顺序严格相反LIFO。四、和atexit的交互#include cstdlib#include iostreamstruct S {S() { std::cout S ctor\n; }~S() { std::cout S dtor\n; }};S s;int main() {std::atexit([]{ std::cout atexit\n; });return 0;}输出S ctoratexitS dtor规则atexit注册的函数和全局对象的析构按逆构造顺序统一编排——谁后注册/构造谁先执行。所以atexit在 main 末尾才注册但它先于s的析构执行。五、写个完整的验证程序#include iostream#include cstdlibstruct Tracer {const char* name;Tracer(const char* n) : name(n) { std::cout ctor name \n; }~Tracer() { std::cout dtor name \n; }};Tracer g1(g1);Tracer g2(g2);Tracer* heap new Tracer(heap);int main() {std::cout --- enter main ---\n;static Tracer s1(s1);std::atexit([]{ std::cout atexit handler\n; });std::cout --- leave main ---\n;return 0;}预期输出ctor g1ctor g2ctor heap--- enter main ---ctor s1--- leave main ---atexit handlerdtor s1dtor g2dtor g1可以观察到g1→g2→heap在 main 前构造堆对象的指针赋值算动态初始化函数内static变量s1是首次执行到时才构造C11 起线程安全析构顺序严格逆序s1→g2→g1heap的析构没出现——印证上一个问题的结论atexit在所有析构之前执行因为它最后注册对于如下代码#include iostream class Widget { public: Widget(){ std::cout constructor ; } ~Widget(){ std::cout destructor ; } }; Widget *widget new Widget; int main() { std::cout main ; return 0; }代码输出constructor main核心原因C 标准规定new创建的对象必须显式delete才会调用析构函数。程序退出时操作系统只是回收进程的物理内存mmap/brk 释放并不会逐个调用 C 对象的析构函数——这是典型的内存泄漏虽然程序马上要退出了所以不会造成实际危害。所以输出就是constructor main缺少destructor。如果我们在main函数中加入:delete widget;#include iostream class Widget { public: Widget(){ std::cout constructor ; } ~Widget(){ std::cout destructor ; } }; Widget *widget new Widget; int main() { std::cout main ; delete widget; return 0; }则会输出constructor main destructor