自定义实现UniquePtr出现double free双重释放错误的原因
问题根因
核心bug出在自行实现的UniquePtr移动构造函数没有对当前新对象的t_成员做初始化,直接对未定义的野指针执行swap操作,违反了独占指针的所有权独占语义,最终触发双重释放错误。
具体逻辑说明
- C++类的非静态成员变量如果不在构造函数初始化列表显式赋值,默认就是未初始化的野值,指向随机内存地址,没有确定值。
- 存在问题的移动构造实现如下:
UniquePtr(UniquePtr&& oth) { std::swap(t_, oth.t_); }
执行swap时,当前正在构造的新对象的t_是随机野指针,交换后被移动的源对象oth会持有这个野指针,而非标准要求的nullptr。
- 当源对象
oth(比如lambda内的局部指针、移动构造产生的临时对象)离开作用域析构时,会对这个野指针执行delete:如果野指针刚好指向之前已经释放过的堆内存,就会触发double free;如果指向其他非法内存地址,也会触发内存访问错误。本次运行时栈内存残留值刚好出现了0(空指针,delete空指针本身是无操作)和已释放的Obj对象地址,因此打印delete 0后触发双重释放报错。 - 前两个测试块未触发问题的原因:第一个块仅涉及直接构造和析构,没有调用移动构造;第二个块在C++17及以上标准中会触发强制拷贝消除,临时对象直接构造在目标对象的内存空间,不会调用移动构造,因此没有踩中bug。
修复方案
修改移动构造函数,在初始化列表先将当前对象的t_置为nullptr再执行交换,保证交换后源对象持有的是空指针,析构时不会释放非法内存:
UniquePtr(UniquePtr&& oth) : t_(nullptr) { std::swap(t_, oth.t_); }
也可以用更直观的所有权转移写法,直接接管源对象的指针后将源对象指针置空:
UniquePtr(UniquePtr&& oth) : t_(oth.t_) { oth.t_ = nullptr; }
补充:当前实现的移动赋值运算符逻辑也不严谨,建议增加自移动判断、提前释放当前对象持有的堆内存后再转移所有权,避免出现内存泄漏或重复释放问题。
内容的提问来源于stack exchange,提问作者weeb
相关产品推荐
相关产品推荐

