动态库卸载时Meyer单例必然失效——析构函数不保证执行,instance成悬垂指针导致段错误;根本原因是dlclose/FreeLibrary不保证调用静态析构器,依赖资源可能已被提前卸载。

动态库卸载时,static Singleton& getInstance() { static Singleton instance; } 这种 Meyer 单例**必然失效**——析构函数大概率不执行,instance 变成悬垂指针,后续访问直接段错误。这不是配置问题,是操作系统和 C++ 运行时对动态库静态析构的明确不保证行为。
为什么 static Singleton instance 在 .so/.dll 里一定不能用
局部静态变量的析构由运行时在模块卸载或进程退出时触发,但 dlclose()(Linux)和 FreeLibrary()(Windows)**不保证调用它**。glibc 常跳过,Windows CRT 在 DLL_PROCESS_DETACH 时也可能跳过堆栈上未显式注册的析构器。
- 现象:卸载后主程序再调用
Singleton::getInstance(),返回地址指向已释放内存,SEGFAULT出现在~Singleton()里调用std::string::clear()或std::mutex::unlock() - 根本原因:单例析构时,它依赖的全局资源(如
std::cout、日志模块、配置单例)可能已被先卸载,符号地址无效 - 更隐蔽的风险:其他插件持有该单例裸指针,卸载后继续解引用,崩溃位置飘忽不定,极难复现
DllMain 中响应 DLL_PROCESS_DETACH 的实操要点
这是 Windows 平台最直接可控的钩子,但必须满足三个硬性条件,否则照样崩溃。
-
lpReserved == nullptr必须判空:只有这个条件为真,才说明是FreeLibrary()主动卸载;若为非空,是进程退出,CRT 堆已不可用,delete会立即崩溃 -
destroyInstance()必须幂等:开头用std::atomic<bool> s_destroyed{false}</bool>+exchange(true),避免多次delete - 销毁顺序必须倒置:先
thread.join()、close(fd)、munmap(),最后才delete m_instance并置m_instance = nullptr - 禁止在
destroyInstance()里调用任何外部 API:包括日志宏、std::cout、网络 SDK、甚至malloc—— 它们底层符号可能已卸载
Linux 下 __attribute__((destructor)) 的真实能力边界
它只在 dlopen 加载的 SO 被 dlclose 时触发,且执行环境极度受限——不是“轻量清理入口”,而是“最后确认哨兵”。
立即学习“C++免费学习笔记(深入)”;
- 编译必须禁用
-fvisibility=hidden,否则该函数会被剥离,清理完全不执行 - 函数体内禁止使用
std::cout、std::mutex、std::string等:它们的静态实例可能已析构或尚未初始化 - 必须配合裸指针管理:
static Singleton* instance = nullptr;,不能用局部静态变量,否则__attribute__((destructor))无法安全访问 - 推荐写法仅做两件事:
if (instance) { delete instance; instance = nullptr; },其余逻辑全部交给显式shutdown()
跨平台唯一可靠方案:显式导出 extern "C" shutdown() 接口
把销毁控制权完全交给宿主程序,是插件系统、热更新等生产场景的事实标准。它规避所有运行时不确定性,但要求宿主严格配对调用。
- 头文件中导出:
extern "C" __declspec(dllexport) void Config_shutdown();(Windows)或extern "C" __attribute__((visibility("default"))) void Config_shutdown();(Linux) -
shutdown()内部只做三件事:检查原子标志、delete实例指针、置空指针;不调用任何跨模块符号 - 宿主必须严格顺序:
dlsym(handle, "Config_shutdown") → shutdown() → dlclose(handle);漏掉shutdown()就等于没卸载 - 若单例持有
std::thread,shutdown()中必须先joinable()判断再join(),不能依赖析构函数隐式处理
真正危险的不是“怎么写销毁逻辑”,而是误信“析构会自动发生”。所有自动机制(static 局部变量、atexit、std::unique_ptr 静态变量)在动态库场景下都不可靠。唯一能掌控的,是让宿主程序在 dlclose 前那一毫秒,亲手调用那个 shutdown() 函数。


















