记使用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超时引发的 ...
随机推荐
- 将Python执行代码打包成exe可执行文件
安装pyinstaller pip3 install pyinstaller 进入py文件目录,执行以下指令 pyinstaller -F -w <文件名.py>,-F代表生成可执行文件, ...
- 面试都在问的「微服务」「RPC」「服务治理」「下一代微服务」一文带你彻底搞懂!
❝ 文章每周持续更新,各位的「三连」是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) ❞ 单体式应用程序 与微服务相对的另一个概念是传统的「单体式应用程 ...
- ADB 调试
1.adb简介 adb的全称为Android Debug Bridge,就是起到调试桥的作用.通过adb我们可以在Eclipse中方面通过DDMS来调试Android程序,说白了就是debug工具.a ...
- UVA11300 Spreading the Wealth 数学
前方数学警告 题目链接:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=25&am ...
- 《Java基础复习》—常识与入门
突然发现自己Java基础的底子不到位,复习! 所记知识会发布在CSDN与博客网站jirath.cn <Java基础复习>-常识与入门 一.Java语言的知识体系图 分为三部分 编程语言核心 ...
- 没用过.gitIgnore还敢自称高级开发?
Git是跟踪项目中所有文件的好工具, 但是,您会希望在项目的整个生命周期中不要跟踪某些文件及其变化. 系统文件(i.e. Mac系统的.Ds_Store) 应用程序配置文件(i.e. app.conf ...
- 监听窗口大小变化,改变画面大小-[Three.js]-[onResize]
如果没有监听窗口变化,将会出现一下情况: 进行处理,这优化了 ...
- SaaS、PaaS、IaaS的含义与区别
先上个图,直观的了解一下 云计算有SPI,即SaaS.PaaS和IaaS三大服务模式. PaaS和IaaS源于SaaS SaaS Software as a Service 软件即服务,提供给客户的服 ...