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

类继承与重载operator delete的对象删除UB问题原因及修复咨询

对象延迟删除机制的未定义行为分析与修复

问题描述

尝试实现对象延迟删除机制时出现未定义行为(UB),不同编译器表现差异明显:部分环境出现段错误,MSVC下AddressSanitizer提示类型不匹配的释放错误。核心代码如下:

#include <iostream>

struct Base
{
    Base()
    {
        std::cout << "Base::ctor\n";
    }
    virtual ~Base()
    {
        std::cout << "Base::dtor\n";
    }
    virtual void invoke() = 0;
};

template <typename T>
class PtrKeeper
{
public:
    PtrKeeper()
    {
        std::cout << "PtrKeeper::ctor\n";
    }
    virtual ~PtrKeeper()
    {
        std::cout << "PtrKeeper::dtor\n";
    }
    void operator delete(void* p) {
        std::cout << "PtrKeeper::operator delete\n";
        destroy(p);
    }
    static void destroy(void* p) {
        delete static_cast<T*>(p);
    }
};

struct Derived
    : public Base
    , public PtrKeeper<Derived>
{
    Derived()
    {
        std::cout << "Derived::ctor\n";
    }
    virtual ~Derived()
    {
        std::cout << "Derived::dtor\n";
    }
    virtual void invoke()
    {
        delete this;
    }
};

int main()
{
    auto obj = new Derived();
    obj->invoke();
    
    return 0;
}

不同编译器表现

  • GCC环境输出:
Base::ctor
PtrKeeper::ctor
Derived::ctor
Derived::dtor
PtrKeeper::dtor
Base::dtor
PtrKeeper::operator delete
  • Clang环境输出:
Base::ctor
PtrKeeper::ctor
Derived::ctor
Derived::dtor
PtrKeeper::dtor
Base::dtor
PtrKeeper::operator delete
Segmentation fault (core dumped)
  • MSVC 2022输出:
Base::ctor
PtrKeeper::ctor
Derived::ctor
Derived::dtor
PtrKeeper::dtor
Base::dtor
PtrKeeper::operator delete
Base::dtor
  • MSVC AddressSanitizer提示:
==21628==ERROR: AddressSanitizer: new-delete-type-mismatch on 0x114eb19a50d0 in thread T3:
  object passed to delete has wrong type:
  size of the allocated type:   48 bytes;
  size of the deallocated type: 24 bytes.

未定义行为原因

  1. 双重删除:
    执行delete this时,流程如下:

    • 首先调用Derived的析构函数,依次触发~Derived()、~PtrKeeper()、~Base(),完成对象析构。
    • 随后调用Derived继承自PtrKeeper<Derived>的operator delete,该函数内部又调用delete static_cast<T*>(p),对已析构释放的对象再次执行delete,导致双重释放内存,触发UB。
  2. 指针类型与偏移错误:
    Derived是多继承类,PtrKeeper<Derived>作为第二个基类,其子对象地址与Derived对象起始地址存在偏移。PtrKeeper::operator delete的参数p若未被正确调整偏移,static_cast<T*>(p)会生成错误指针,访问不属于该对象的内存,这也是ASAN提示类型大小不匹配的原因。

修复方案(保留原设计思路)

核心目标是避免双重删除,确保指针操作正确性,以下是两种可行方案:

方案1:修正PtrKeeper::operator delete实现

将operator delete改为直接调用全局内存释放函数,不再重复删除对象(析构已完成):

template <typename T>
class PtrKeeper
{
public:
    // 其他成员不变
    void operator delete(void* p) {
        std::cout << "PtrKeeper::operator delete\n";
        // 调用全局operator delete释放内存,避免重复删除
        ::operator delete(p);
    }
    // 调整destroy方法,避免触发重复删除
    static void destroy(void* p) {
        // 若需保留destroy,可改为手动析构+释放(需确保仅调用一次)
        // T* obj = static_cast<T*>(p);
        // obj->~T();
        // ::operator delete(obj);
    }
};

方案2:调整调用逻辑,由PtrKeeper统一管理销毁

修改Derived::invoke直接调用PtrKeeper的销毁方法,让destroy手动完成析构和内存释放,避免依赖默认delete流程:

template <typename T>
class PtrKeeper
{
public:
    // 其他成员不变
    // 移除自定义operator delete,避免与默认流程冲突
    static void destroy(void* p) {
        T* obj = static_cast<T*>(p);
        // 手动调用析构函数
        obj->~T();
        // 调用全局operator delete释放内存
        ::operator delete(obj);
    }
};

struct Derived
    : public Base
    , public PtrKeeper<Derived>
{
    // 其他成员不变
    virtual void invoke()
    {
        // 直接调用PtrKeeper的destroy方法,而非delete this
        PtrKeeper<Derived>::destroy(this);
    }
};

两种方案均可避免双重删除和指针偏移问题,同时保留PtrKeeper管理对象销毁的设计思路。

内容的提问来源于stack exchange,提问作者Aleksey Ivanov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 19:43:02