记使用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超时引发的 ...
随机推荐
- 武林高手?敏捷开发,唯velocity取胜
去年学习软件工程教材,初识敏捷开发.目前所在的团队,也是使用敏捷开发方式.今天读了这篇文章,对于这段时间的效率有所警醒.其次,个人认同文章中的用户故事,结对编程思想. 文章援引:http://gitb ...
- 使用RandomString方法后,结果返回相同的随机数解决办法
所遇问题: 在做超市管理系统的登录项目时,在对“随机数的产生”出现一个问题,在产生多个随机数的时候,出现了产生了多个一样的随机数,具体代码如下: /// <summary> /// 生成随 ...
- 201771030106-葛佳诚 实验一 软件工程准备-<初读《构建之法——现代软件工程》有问>
项目 内容 课程班级博客链接 https://edu.cnblogs.com/campus/xbsf/nwnu2020SE 作业要求链接 https://www.cnblogs.com/nwnu-da ...
- VUE一款适用于pc平台的简单toast
新项目要求用typescript+vue+elementui的模式来搭建pc项目,最初踩了好多坑.产品说提示不想用element-ui的提示. 打算用toast的形式.所以就自己写了一个pc的toas ...
- 如何用VmwareWorkstation安装Centos系统
教你如何安装虚拟机系统 首先你得有虚拟化软件,常用的VmwareWorkstation一般能满足日常需求. 下载地址,请自行搜索. 第一步,新建虚拟机 选择安装系统源 这里有三个选项. 1.第一个是使 ...
- Centos7.x & RedHat7.x系统忘记 root 密码解决办法
重启系统进入引导页面 先将机器重启 根据提示按下e进入内核编辑页面 找到linux16参数行,并在行尾加上rd.break,之后按下Ctrl+X重启 如上图所示,重启之后将进入救援模式. 这是依次输入 ...
- 初识docker与理解
因最近公司的一个新项目,有一个业务场景是需要给多个甲方的服务器配置运行环境与部署,所以考虑使用docker来实现环境配置的统一 1.docker是什么 docker是一种容器虚拟化技术的实现,相当于在 ...
- ThinkPHP3.2.3集成微信分享JS-SDK实践
先来看看微信分享效果:在没有集成微信分享js-sdk前是这样的:没有摘要,缩略图任意抓取正文图片 在集成微信分享js-sdk后是这样的:标题,摘要,缩略图自定义 一.下载微信SDK开发包下载地址:ht ...
- Python入门学习指导(VS Code配置向)
代码编辑器或IDE 推荐Vs Code,Atom和Sublime(本文以Vs Code为例,Sublime对中文支持不是很好,时常弄好了Sublime的乱码,却在复制到其他编辑器时出了问题) Vs C ...
- 【python实现卷积神经网络】padding2D层实现
代码来源:https://github.com/eriklindernoren/ML-From-Scratch 卷积神经网络中卷积层Conv2D(带stride.padding)的具体实现:https ...