最近由于业务需要在写内存池子时遇到了一个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. Selenium系列(十四) - Web UI 自动化基础实战(1)

    如果你还想从头学起Selenium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1680176.html 其次,如果你不懂前端基础知识, ...

  2. 集合和映射(Set And Map)

    目录 集合 Set 基于二分搜索树实现集合 基于链表实现集合 集合的时间复杂度分析 映射 Map 基于链表实现映射 基于二分搜索树实现映射 映射的时间复杂度分析 leetcode上关于集合和映射的问题 ...

  3. 思考设计SQL优化方案

    一.优化的哲学 注:优化有风险,涉足需谨慎 1.优化可能带来的问题? 优化不总是对一个单纯的环境进行,还很可能是一个复杂的已投产的系统: 优化手段本来就有很大的风险,只不过你没能力意识到和预见到: 任 ...

  4. 模块 schedule 定时任务

    schedule模块实现定时任务 2018-08-29 15:01:51 更多 一.官方示例 import schedule import time def job(): print("I' ...

  5. Linux和Windows环境下安装Nginx

    Nginx的安装 windows环境下安装Nginx 安装 Nginx 的下载地址如下: http://nginx.org/en/download.html 选择时尽量选择 Stable 稳定版本,点 ...

  6. Java实验五参考答案

    1.找错误 btOK.setOnAction( new EventHandler<ActionEvent> { public void handle (ActionEvent e) { S ...

  7. Java并发基础06. 线程范围内共享数据

    假设现在有个公共的变量 data,有不同的线程都可以去操作它,如果在不同的线程对 data 操作完成后再去取这个 data,那么肯定会出现线程间的数据混乱问题,因为 A 线程在取 data 数据前可能 ...

  8. Python 程序慢的像蜗牛,我该怎么办?

    1. “一猿小讲”的风格就是多元化,偶尔会真情吐露一下程序猿的内心:偶尔也结合自己的经历畅聊一些经验杂谈:其中也不乏幽默风趣的技术故事.分享是件快乐的事情,工作之余,有时间我就尽力多码字,多推几篇文章 ...

  9. 001_Three.js中的跨域问题

    001_Three.js中的跨域问题 [情景描述]: 在初始化模型,引入字体和纹理皮肤图片的时候,由于跨域问题,出现了以下提示: Access to image at 'file:///F:/User ...

  10. ELK 是什么?

                                                                         ELK 是什么? 2018年07月04月  09:37:46 ...