记使用STL与unique_ptr造成的事故-段子类比
最近由于业务需要在写内存池子时遇到了一个doule-free的问题。折腾半个晚上以为自己的眼睛花了。开始以为是编译器有问题(我也是够自信的),但是在windows下使用qtcreator vs2017 和Linux下 使用gcc纷纷编译执行得到相同的结果。有一点要说的是使用gcc和qtcreator(mingW)虽然都double-free了,但是都没有给出错误的执行代码,vs在执行到析构函数时却可以给出给出异常提示。不得不说vs的运行检查更严格一些。下面我们先说正事后聊段子。
下面先贴出代码,如果你能一眼看出问题,请在评论里叫我一声”弟弟“
#include <list>
#include <mutex>
#include <algorithm>
#include <memory>
#include <assert.h>
#include <iostream> using namespace std; template<typename T>
class DataObjectPool
{
public:
DataObjectPool(){}
~DataObjectPool(){} public:
T* Get()
{
std::lock_guard<std::mutex> lockGuard(m_mutex);
typename PtrList::iterator it = m_freeList.begin();
std::unique_ptr<T> memoryPtr;
T* pReturn = nullptr; if (it == m_freeList.end())
{
memoryPtr.reset(new T());
}
else
{
memoryPtr.reset((*it).release());
m_freeList.pop_front();
} pReturn = memoryPtr.get();
m_usedList.push_back(std::move(memoryPtr));
return pReturn;
} void Free(T* pData)
{
std::lock_guard<std::mutex> lockGuard(m_mutex); std::unique_ptr<T> memoryPtr;
memoryPtr.reset(pData); auto itr = std::find(m_usedList.begin(),m_usedList.end(),memoryPtr); if(itr != m_usedList.end())
{
m_freeList.push_back(std::move(memoryPtr));
m_usedList.erase(itr);
}
}
private:
typedef std::list<std::unique_ptr<T>> PtrList;
private: std::mutex m_mutex;
PtrList m_usedList;
PtrList m_freeList;
}; class Test
{
public:
Test()
{
cout << "Test()" << endl;
}
~Test()
{
cout << "~Test()" << endl;
}
}; int main(int argc, char *argv[])
{
DataObjectPool<Test> PoolTest;
auto pt1 = PoolTest.Get();
auto pt2 = PoolTest.Get();
PoolTest.Free(pt1);
PoolTest.Free(pt2); }
运行结果:
Test()
Test()
~Test()
~Test()
~Test()
~Test()
D:\workspace\Test\build-Test2-Desktop_Qt_5_8_0_MinGW_32bit-Debug\debug\Test2.exe exited with code
看代码太麻烦,这里画出逻辑图,肯定是Free过程中除了什么问题,代码对图再看一下。


分析:
1) memoryPtr通过reset方法获取到指针后通过std::move(memoryPtr)将指针转移了,所以它不会释放指针。
2) list::erase方法删除迭代器确实会释放释放相关内存,可是内存在释放之前不是已经std::move吗?
问题的关键就在这里:
std::unique_ptr<T> memoryPtr 和 auto itr迭代器所包裹的指针是一样的,即都包裹了pData,可是他们根本不是一个东西。而是两个对象,看下面这段代码,可以更清楚:
#include <iostream>
#include <memory>
using namespace std; class Test
{
public:
Test()
{
cout<<"Test()"<<endl;
}
~Test()
{
cout<<"~Test()"<<endl;
}
};
int main()
{
Test *t = new Test(); unique_ptr<Test> upt1; //!依照上面的模型upt1可以看作是某个迭代器它包含t指针;
upt1.reset(t);
unique_ptr<Test> upt2; //!后来者对t也宣布了所有权
upt2.reset(t); if(upt1 == upt2)
{
cout<<upt1.pointer()<<endl;
cout<<"upt1==upt2"<<endl;
}
}
一个裸指针在丢给一个unique_ptr对象之前,该unique_ptr认为对该指针所有权是唯一的,当多个unique_ptr对象引用同一个裸的指针是无法检查该指针被谁引用,这一点,所以也double free也就不稀奇了。
奇怪的是虽然std::unique_ptr是指针的所有权是独占的,指针只能转移,但是其却重载了operator ==运算符。既然是独占的,也就是std::unique_ptr不能在逻辑上承认多个unique_ptr对象对同一指针同时占有。这个操作让人非常疑惑。这也就是代码"auto itr = std::find(m_usedList.begin(),m_usedList.end(),memoryPtr)"能够返回有效迭代器和"if(upt1 == upt2)"能够成立的原因了。下面贴出std::unique_ptr "=="操作符重载的实现:

其内部是对两个裸指针的比较,让人费解的实现。
回归正题,原因弄明白了,我们来修改一下来让来避免这个问题:


推荐一篇文章:
《C++ 智能指针的正确使用方式》总结的不错!
https://www.cyhone.com/articles/right-way-to-use-cpp-smart-pointer/
记使用STL与unique_ptr造成的事故-段子类比的更多相关文章
- 记一些stl的用法(持续更新)
有些stl不常用真的会忘qwq,不如在这里记下来,以后常来看看 C++中substr函数的用法 #include<string> #include<iostream> usin ...
- 记一次zabbix-server故障恢复导致的事故 zabbix-server.log -- One child process died
前言 zabbix-server昨天出了个问题,不停的重启.昨天摆弄到晚上也不搞清楚原因,按照网上说的各种操作,各种CacheSize.TimeOut.StartPollers都改了,还有什么Incl ...
- 记一次oracle crs无法重启事故
今天在修改了数据库参数后,关闭数据库及crs,然后重新启动了服务器,服务器启动完成之后,发现数据库无法启动,过程如下: step1:重启数据库 $ su - grid $ srvctl stop da ...
- 记一次ORACLE无法启动登陆事故
打开XSHELL 登陆ORACLE用户 1.sqlplus scott/scott 提示登陆失败 2.sqplus / as sysdba 启动数据库提示 3.查找日志 操作日志:$ORACLE_HO ...
- 记一下STL的一个题
A. Diversity time limit per test 1 second memory limit per test 256 megabytes input standard input o ...
- 评CSDN上一篇讲述数据迁移的文章“程序员 12 小时惊魂记:凌晨迁移数据出大事故!”
原文地址:https://blog.csdn.net/csdnnews/article/details/98476886 我的评论:热数据迁移,本不该搞突击,这样一旦出现问题后果不堪设想,多少DBA和 ...
- 记一次真实的线上事故:一个update引发的惨案!
目录 前言 项目背景介绍 要命的update 结语 前言 从事互联网开发这几年,参与了许多项目的架构分析,数据库设计,改过的bug不计其数,写过的sql数以万计,从未出现重大纰漏,但常在河边走,哪 ...
- 再记一次 应用服务器 CPU 暴高事故分析
一:背景 1. 前言 大概有2个月没写博客了,不是不想写哈
- 记go中一次http超时引发的事故
记一次http超时引发的事故 前言 分析下具体的代码实现 服务设置超时 客户端设置超时 http.client context http.Transport 问题 总结 参考 记一次http超时引发的 ...
随机推荐
- NatApp 外网映射工具
外网映射工具 在做微信开发或者是对接第三方支付接口时,回调接口可能需要外网访问,这时候开发者在本地测试的时候,需要用到外网测试工具.常用的外网测试工具有natapp.ngrok NatApp简介服务器 ...
- Eclipse无法查看第三方jar包源代码解决
我在csdn写了过了:https://blog.csdn.net/weixin_40404606/article/details/105174820
- Spring Framework之IoC容器
Spring IoC 概述 问题 1.什么是依赖倒置? 2.什么是控制反转? 3.什么是依赖注入? 4.它们之间的关系是怎样的? 5.优点有哪些? 依赖倒置原则 (Dependency Inversi ...
- 了解PCI Express的Posted传输与Non-Posted传输
0.写在前面 本文首发于公众号[两猿社],后续将在公众号内持续更新~ 其实算下来接触PCIe很久了,但是由于之前换工作,一直没有系统的学习和练手项目,现在新项目买了Synopsys的PCIe IP,总 ...
- C 苟富贵
时间限制 : 15000 MS 空间限制 : 524288 KB 问题描述 你最近买六合彩赚了很多钱,导致一个银行账户存不下了,于是你开设了 N 个账户,第 i 个账户里存有 Ai 元. 你的好友 ...
- EOS基础全家桶(六)账号管理
简介 本篇我们会学习最基本的账号相关的操作,包括了创建账号和查询,关于账号资源的操作因为必须先部署系统合约,所以我们会留到后面单独写一篇来讲解. 6-EOS基础全家桶(六)账号管理 简介 账号介绍 账 ...
- C语言:signed和unsigned的区别
参考博客:https://blog.csdn.net/wen381951203/article/details/79922220 signed和unsigned用于修饰整数类型(包括char,从ANS ...
- vue post请求 参数带有中文后端无法接收或者收到乱码,无法返回数据问题
问题来源: 在使用axios时,和java联调,发现调接口服务器始终拿不到参数data,但是检查network也的确传了data,才有了该文章. 基于 vue-axios 和 $.ajax 两种请求方 ...
- 配置samba和NFS共享服务
配置samba和NFS共享服务 1案例1:配置SMB文件夹共享 1.1问题 本例要求在虚拟机server0上发布两个共享文件夹,具体要求如下: 此服务器 ...
- django禁用csrf
django禁用csrf 函数试图 from django.views.decorators.csrf import csrf_exempt @csrf_exempt def your_func_vi ...