zoukankan      html  css  js  c++  java
  • 记使用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:workspaceTestuild-Test2-Desktop_Qt_5_8_0_MinGW_32bit-DebugdebugTest2.exe exited with code 0

    看代码太麻烦,这里画出逻辑图,肯定是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/

     

     

     

     

     

     

     

  • 相关阅读:
    二维数组输出10行杨辉三角
    二维数组的练习----求和
    数组的异常及处理
    二维数组在内存中的结构
    Ubuntu系统中安装Mercurial 以支持hg
    什么是插补、直线插补、联动与插补
    压力表(负压表...)
    常用接近开关的原理和分类
    VMware Ubuntu安装详细过程
    Redis+Spring缓存实例(windows环境,附实例源码及详解)
  • 原文地址:https://www.cnblogs.com/wangkeqin/p/12787548.html
Copyright © 2011-2022 走看看