最近由于业务需要在写内存池子时遇到了一个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造成的事故-段子类比的更多相关文章

  1. 记一些stl的用法(持续更新)

    有些stl不常用真的会忘qwq,不如在这里记下来,以后常来看看 C++中substr函数的用法 #include<string> #include<iostream> usin ...

  2. 记一次zabbix-server故障恢复导致的事故 zabbix-server.log -- One child process died

    前言 zabbix-server昨天出了个问题,不停的重启.昨天摆弄到晚上也不搞清楚原因,按照网上说的各种操作,各种CacheSize.TimeOut.StartPollers都改了,还有什么Incl ...

  3. 记一次oracle crs无法重启事故

    今天在修改了数据库参数后,关闭数据库及crs,然后重新启动了服务器,服务器启动完成之后,发现数据库无法启动,过程如下: step1:重启数据库 $ su - grid $ srvctl stop da ...

  4. 记一次ORACLE无法启动登陆事故

    打开XSHELL 登陆ORACLE用户 1.sqlplus scott/scott 提示登陆失败 2.sqplus / as sysdba 启动数据库提示 3.查找日志 操作日志:$ORACLE_HO ...

  5. 记一下STL的一个题

    A. Diversity time limit per test 1 second memory limit per test 256 megabytes input standard input o ...

  6. 评CSDN上一篇讲述数据迁移的文章“程序员 12 小时惊魂记:凌晨迁移数据出大事故!”

    原文地址:https://blog.csdn.net/csdnnews/article/details/98476886 我的评论:热数据迁移,本不该搞突击,这样一旦出现问题后果不堪设想,多少DBA和 ...

  7. 记一次真实的线上事故:一个update引发的惨案!

    目录 前言 项目背景介绍 要命的update 结语 前言   从事互联网开发这几年,参与了许多项目的架构分析,数据库设计,改过的bug不计其数,写过的sql数以万计,从未出现重大纰漏,但常在河边走,哪 ...

  8. 再记一次 应用服务器 CPU 暴高事故分析

    一:背景 1. 前言 大概有2个月没写博客了,不是不想写哈

  9. 记go中一次http超时引发的事故

    记一次http超时引发的事故 前言 分析下具体的代码实现 服务设置超时 客户端设置超时 http.client context http.Transport 问题 总结 参考 记一次http超时引发的 ...

随机推荐

  1. 使用FME对CAD管网数据进行过滤、聚合、中心点替换

    1.首先加载CAD数据,并暴露出需要使用到的相关字段.比如:block_number.fme_geometry.fme_color等字段. 2.对一个元素有多种类型部件的需要进行过滤,例如本次的检修井 ...

  2. HTTP 请求状态码

    200    请求成功 304    从缓存中读取 302 + 响应头中定义location: 重定向 // 自定义重定向 @RequestMapping("/customRedirecti ...

  3. VLAN基础

    VLAN(Virtual Local Area Network)的中文名为"虚拟局域网".是将一个物理的局域网在逻辑上划分成多个广播域,从而实现二层隔离的技术. 一.VLAN的优点 ...

  4. 学习方法,学习方式By:ラピスラズリ(Dawn)20200407

    原创,转载请注明,谢谢!

  5. B. Lost Number【CF交互题 暴力】

    B. Lost Number[CF交互题 暴力] This is an interactive problem. Remember to flush your output while communi ...

  6. Elasticsearch 核心术语概念

    Elasticsearch 相当于一个关系型数据库 索引 index 类型 type 文档 document 字段 fields 跟关系型数据库对比 Elasticsearch 相当于一个数据库 索引 ...

  7. Prthon多线程和模块

    Prthon多线程和模块 案例1:简化除法判断 案例2:分析apache访问日志 案例3:扫描存活主机 案例4:利用多线程实现ssh并发访问 1 案例1:简化除法判断 1.1 问题 编写mydiv.p ...

  8. Scratch2的鸡兔同笼

    解题思路鸡兔同笼新算法:已知共有鸡和兔15只,共有40只脚,问鸡和兔各有几只.算法:假设鸡和兔训练有素,吹一声哨,它们抬起一只脚,(40-15=25) .再吹一声哨,它们又抬起一只脚,(25-15=1 ...

  9. 在java 中一种简单方式的声明静态Map常量的方法

    我现在需要在一个类里面放一个HashMap,往里面放一些数据,每次要从数据库中取数据的时候先查找HashMap,看是否已经存在,若存在就直接提取,若不存在就从数据库中抽取数据之后再放到HashMap中 ...

  10. MyBatis通用 Mapper4使用小结

    官网地址: http://www.mybatis.tk/ https://gitee.com/free 1.使用springboot,添加依赖: 使用tk的mybatis后不需要引用官方原生的myba ...