Skip to content

动态库卸载导致 shared_ptr 析构崩溃问题分析

问题现象

image.png

程序在退出时发生 SIGSEGV(段错误),崩溃位置位于 std::shared_ptr<BusinessPlugin> 的析构过程中。

根本原因

  • BusinessPlugin 是从 动态库(. so) 中加载的类。
  • shared_ptr 对象析构 之前,该动态库已被显式调用 dlclose() 卸载。
  • 导致 BusinessPlugin 的析构函数地址失效(所处内存已被释放或 unmapped)。
  • shared_ptr 尝试调用该析构函数时,跳转到非法内存地址 → 触发 段错误

关键细节

image.png

  • 成员变量声明顺序影响析构顺序(C++ 标准规定:成员按声明顺序构造,逆序析构)。
  • 若动态库句柄(如 void* handle = dlopen(...))作为类成员,且声明在 shared_ptr<BusinessPlugin> 之后,则:
    • shared_ptr 先析构 → 需要调用 BusinessPlugin::~BusinessPlugin()
    • 此时动态库可能已被 dlclose(handle) 卸载(如果 handle 在之后才析构)→ 崩溃。

解决方案

  1. 调整成员声明顺序
    确保动态库句柄(dlopen 返回的 handle)在 shared_ptr<BusinessPlugin> 之前声明
    这样 handle 会在 shared_ptr 之后析构,保证析构 BusinessPlugin 时动态库仍加载。

  2. 延迟卸载动态库
    手动管理生命周期,确保所有依赖该库的对象(如 shared_ptr)完全析构后,再调用 dlclose()

  3. 避免在动态库中定义需跨模块析构的类型
    可考虑使用 纯虚接口 + 工厂函数 模式,将对象创建和销毁限制在动态库内部。

示例(正确顺序)

cpp
class PluginManager {
    void* lib_handle;               // 先声明:后析构
    std::shared_ptr<BusinessPlugin> plugin; // 后声明:先析构
public:
    PluginManager() {
        lib_handle = dlopen("libbusiness.so", RTLD_LAZY);
        // ... 加载 plugin
    }
    ~PluginManager() {
        // plugin 先析构(此时 lib_handle 仍有效)
        // lib_handle 后析构,随后 dlclose
    }
};

⚠️ 注意:C++ 对象析构顺序与成员声明顺序相反

基于 VitePress 构建