关于内存泄漏---auto_ptr

来源:互联网 发布:临沂蓝狐网络怎么样 编辑:程序博客网 时间:2024/06/10 23:22

        上一篇,自己写了一些代码,在调试的时候去检测内存泄漏,现在来分析一下auto_ptr

        源码是vs2015里的。ok,let's go !

template<class _Ty>struct auto_ptr_ref//引用指针{explicit auto_ptr_ref(_Ty *_Right)//只能显示调用,用来初始化成员变量: _Ref(_Right){}_Ty *_Ref;//成员变量,是一个_Ty类型的指针};
template<class _Ty>class auto_ptr;//声明模板类,auto_ptr
template<class _Ty>class auto_ptr{// wrap an object pointer to ensure destructionpublic:typedef auto_ptr<_Ty> _Myt;//_Myt ==> my typetypedef _Ty element_type;explicit auto_ptr(_Ty *_Ptr = 0) _THROW0()//必须显示调用,初始化成员变量_Myptr, : _Myptr(_Ptr)// auto_ptr<std::string>(new std::string()) one;                {// construct from object pointer// one = new std::string(); 非法}// one = auto_ptr<std::string>(new std::string()); 显示调用auto_ptr(_Myt& _Right) _THROW0()//this._Myptr = _Right._Myptr; _Right._Myptr = 0;: _Myptr(_Right.release()){// construct by assuming pointer from _Right auto_ptr}auto_ptr(auto_ptr_ref<_Ty> _Right) _THROW0(){// construct by assuming pointer from _Right auto_ptr_ref_Ty *_Ptr = _Right._Ref;_Right._Ref = 0;// release old_Myptr = _Ptr;// reset this}template<class _Other>operator auto_ptr<_Other>() _THROW0(){// convert to compatible auto_ptrreturn (auto_ptr<_Other>(*this));}template<class _Other>operator auto_ptr_ref<_Other>() _THROW0(){// convert to compatible auto_ptr_ref_Other *_Cvtptr = _Myptr;// test implicit conversionauto_ptr_ref<_Other> _Ans(_Cvtptr);_Myptr = 0;// pass ownership to auto_ptr_refreturn (_Ans);}template<class _Other>_Myt& operator=(auto_ptr<_Other>& _Right) _THROW0(){// assign compatible _Right (assume pointer)reset(_Right.release());return (*this);}template<class _Other>auto_ptr(auto_ptr<_Other>& _Right) _THROW0(): _Myptr(_Right.release()){// construct by assuming pointer from _Right}_Myt& operator=(_Myt& _Right) _THROW0(){// assign compatible _Right (assume pointer)reset(_Right.release());return (*this);}_Myt& operator=(auto_ptr_ref<_Ty> _Right) _THROW0(){// assign compatible _Right._Ref (assume pointer)_Ty *_Ptr = _Right._Ref;_Right._Ref = 0;// release oldreset(_Ptr);// set newreturn (*this);}~auto_ptr() _NOEXCEPT{// destroy the objectdelete _Myptr;}_Ty& operator*() const _THROW0(){// return designated valuereturn (*get());}_Ty *operator->() const _THROW0(){// return pointer to class objectreturn (get());}_Ty *get() const _THROW0(){// return wrapped pointerreturn (_Myptr);}_Ty *release() _THROW0(){// return wrapped pointer and give up ownership_Ty *_Tmp = _Myptr;_Myptr = 0;return (_Tmp);}void reset(_Ty *_Ptr = 0){// destroy designated object and store new pointerif (_Ptr != _Myptr)delete _Myptr;_Myptr = _Ptr;}private:_Ty *_Myptr;// the wrapped object pointer};

源码如上,说实在,看了源码就很容易理解了,好我们用例子,和反汇编来分析

先写一个测试类


#include <memory>
#include <stdio.h>
class test
{
public: test()
{
    printf("test()\n");
~test()
{
     printf("~test()\n");
void testFunc()
 {  
     printf("test::testFunc()\n"); 
}
};
测试类如上


为了便于后面的反汇编分析,我们先来分析一下test类


test类的内存结构,无虚函数,无变量,所以test将占用1字节内存。

inline void printfInt(int i){printf("%d\n", i);}void main(){printfInt(sizeof(test));test* pTest = new test();pTest->testFunc();}
反汇编:

test* pTest = new test();00C61080  push        1  00C61082  call        operator new (0C610B5h)  00C61087  push        offset string "test()\n" (0C62190h)  00C6108C  call        printf (0C61040h)//new test()

test类的各个函数的返汇编代码:

void main(){ test l_test; l_test.testFunc(); //l_test.~test(); 局部变量最后会被析构  }   
test::test() 函数的反汇编,经过编译器的优化,直接调用 printf("test()\n");
test l_test;00BC1070  push        offset string "test()\n" (0BC2118h)  00BC1075  call        printf (0BC1040h) 
 test::testFunc() 也是直接调用 printf("test::testFunc()\n");
l_test.testFunc();00BC107A  push        offset string "test::testFunc()\n" (0BC212Ch)  00BC107F  call        printf (0BC1040h) 
test::~test() 也是直接俄调用 printf("test::~test()\n");
 //l_test.~test(); 局部变量最后会被析构  }    00BC1084  push        offset string "~test()\n" (0BC2120h)  00BC1089  call        printf (0BC1040h) 

现在一个一个函数 来剖解 auto_ptr 类;

存储用内存来存,对这些内存的操作用函数。别跟我说,你用的是 c++,java,php等等。 

首先,我们需要知道 一个auto_ptr 对象,会占用多大的内存,没有虚函数,一个指针变量,32为系统,当然就是4字节了,你不信吗? 上代码

inline void printfInt(int i){ printf("%d\n", i);}
void main(){ printfInt(sizeof(std::auto_ptr<char>)); //输出 4} 

ok,来分析 auto_ptr_ref类
template<class _Ty>struct auto_ptr_ref{// proxy reference for auto_ptr copyingexplicit auto_ptr_ref(_Ty *_Right): _Ref(_Right){// construct from generic pointer to auto_ptr ptr}_Ty *_Ref;// generic pointer to auto_ptr ptr};
一个指针变量,占用4字节。

ok,我们来分析 auto_ptr类的 函数吧。

explicit auto_ptr(_Ty *_Ptr = 0) _THROW0()  : _Myptr(_Ptr)  { // construct from object pointer  }

_Myptr 是 _Ty* 类型的私有变量,这个函数必须显示调用。无参的时候,_Myptr将被置为0;

我们来测试一下,测试代码:

template<class _Ty>inline void printfAddr(_Ty* addr){printf("%d\n", reinterpret_cast<int>(addr));}void main(){ typedef std::auto_ptr<test> test_t; test_t ap_test;  //_Myptr = 0; printfAddr(ap_test.get());    // 输出 0 值 printfAddr(test_t(new test()).get()); //输出非 0 值}

ok 我们来看一下反汇编(release) 

经过编译器优化 直接 执行_Myptr = 0 ,没有压栈弹栈操作。 printfAddr(ap_test.get())  直接优化成 printf("%d\n",_Myptr);
test_t ap_test;//_Myptr = 0;01271099  mov         dword ptr [ebp-14h],0  printfAddr(ap_test.get());// 输出 0 值012710A0  push        0  012710A2  push        offset string "%d\n" (012721A4h)  
printfAddr(test_t(new test()).get());//输出非 0 值012710B3  push        1  012710B5  call        operator new (0127113Fh)012710BA  mov         esi,eax  //分配test类内存。012710BC  push        offset string "test()\n" (01272190h)  012710C1  mov         dword ptr [ap_test],esi  //保存分配的 test* 指针012710C4  call        printf (01271040h)  //调用test构造函数012710C9  push        esi  012710CA  push        offset string "%d\n" (012721A4h)  012710CF  call        printf (01271040h)//printf("%d\n",_Myptr)

ok 我们来看一下auto_ptr 的析构函数 和 get()函数

~auto_ptr() _NOEXCEPT{// destroy the objectdelete _Myptr;//直接删除对象}
 _Ty *get() const _THROW0()  { // return wrapped pointer  return (_Myptr);//直接返回 对象指针  }

思考,其中的bug ,来上代码:

template<class _Ty>inline void printfAddr(_Ty* addr){printf("%d\n", reinterpret_cast<int>(addr));}template<class _Ty>void auto_ptr_bug(_Ty* pTy){_Ty* l_pTy = std::auto_ptr<_Ty>(pTy).get();printfAddr(l_pTy);}void main(){test* pTest = new test();auto_ptr_bug(pTest);delete pTest;//良好的编程风格喔,竟然出bug}

为什么,会出问题尼,因为 auto_ptr_bug函数里使用 std::auto_ptr,已经把 pTest对象delete了,所以最后delete pTest 就出问题了

所以我们需要改写  explicit auto_ptr(_Ty *_Ptr = 0) 构造函数,_Ptr只能为右值,不能为左值,因为左值,可能还被其他引用。

我们再来看另一种bug

void main(){typedef std::auto_ptr<std::string> ap_string_t;ap_string_t pString = ap_string_t(new std::string("string"));ap_string_t pString1 = pString;// operator= 参数应该为右引用printf(pString->c_str());//pString 指针被置空了,所以出现bug了}

看auto_ptr使用不当,会带来很多的问题,本来想避免内存泄漏的问题,却在使用不当的情况下,引入了更多的bug,但auto_ptr管理内存还是很高效的,关键要注意的是,我们不能把生存时间比auto_ptr变量长的指针变量作为auto_ptr构造函数的参数,所以要限制指针变量为右值,不能把 为左值的auto_ptr变量赋值给其他的auto_ptr变量.

ok ! 接着我们来分析一下 auto_ptr的兼容性

template<class _Other>operator auto_ptr<_Other>() _THROW0(){// convert to compatible auto_ptrreturn (auto_ptr<_Other>(*this));}template<class _Other>_Myt& operator=(auto_ptr<_Other>& _Right) _THROW0(){// assign compatible _Right (assume pointer)reset(_Right.release());return (*this);}template<class _Other>auto_ptr(auto_ptr<_Other>& _Right) _THROW0(): _Myptr(_Right.release()){// construct by assuming pointer from _Right}

上面这几个函数就是为了兼容性而写的,比如 

可以这么写,auto_ptr也是可以的。

#include <memory>#include <string>#include <stdio.h>
class parent{public: virtual void name() {  printf("parent\n"); }};
class son :public parent{public: void name() override {  printf("son\n"); }};
void main(){ typedef parent* parent_ptr; typedef son*    son_ptr;
 parent_ptr pParent; son_ptr  pSon = new son(); pParent = pSon; pSon = (son_ptr)pParent; //强制转换
 std::auto_ptr<parent> apParent; std::auto_ptr<son>   apSon(new son()); std::auto_ptr<parent> apParent1(new son());
 apParent = apSon; //子类转换成父类,但apSon不能再被使用 apParent->name();
 // apSon->name(); 运行时出错 // apSon = apParent; 编译出错}    


ok !  operator= 很简单,就不说了, 我们来看一下,最精华的部分.  release() , ~auto_ptr()

_Ty *release() _THROW0(){// return wrapped pointer and give up ownership_Ty *_Tmp = _Myptr;_Myptr = 0;return (_Tmp);}~auto_ptr() _NOEXCEPT{// destroy the objectdelete _Myptr;}

你会说 ,这两眼就看懂了,有什么好精华的。呵呵,很多越是简单的东西,越是最让人忽略的东西。在我看来,没有这精妙的写法,auto_ptr根本不能生存。
当然了,看到 ~auto_ptr()函数的时候,我第一直接是,不用判断 _Myptr 是否为空指针吗。就这样直接delete吗。
ok, 我们来分析一下吧。
首先,看一下下面的代码:
void main(){std::auto_ptr<std::string> ap_string;}

上面的代码竟然没有bug, 对我而言,这是不可能的事,我觉得一定有bug,为什么呢,因为你,调用了auto_ptr(_Ty* rTy = 0) 的构造函数,那么_Myptr一定等于0,那么在析构函数被调用的时候。delete _Myptr 一定会出错啊(或许,一些高手已经看懂了,我为什么会这么认为的原因)。起初,我以为是release版本优化了,于是,我有换成debug版本,一步一步的跟踪,没错啊,是delete 0 啊,怎么不出错啊。卧槽。见鬼了。 说实在,不知什么时候开始,我习惯了 这样写代码。if(ptr != nullptr) { delete ptr;} 所以,我一直以为,delete 0 是错误的。最后带着疑惑,我百度了 delete 0 ,这不是一个错误的语句。

ok,我们看一下,release() 函数,都是被那些函数引用了

auto_ptr(_Myt& _Right) _THROW0(): _Myptr(_Right.release()){// construct by assuming pointer from _Right auto_ptr}template<class _Other>_Myt& operator=(auto_ptr<_Other>& _Right) _THROW0(){// assign compatible _Right (assume pointer)reset(_Right.release());return (*this);}template<class _Other>auto_ptr(auto_ptr<_Other>& _Right) _THROW0(): _Myptr(_Right.release()){// construct by assuming pointer from _Right}_Myt& operator=(_Myt& _Right) _THROW0(){// assign compatible _Right (assume pointer)reset(_Right.release());return (*this);}

都是一些 ,以auto_ptr类型为参数的函数,进来的都是 auto_ptr类型的对象,这些对象把_Myptr 赋值给this._Myptr, 如果不用release 那么将有两个对象同时引用了指针变量,因为,这两个变量都是auto_ptr对象,最后都会调用,delete _Myptr.(两次) 第二次调用的时候,就是崩溃了。所以需要把 参数变量里的 _Myptr = 0;因为 delete 0 没有什么影响,所以。auto_ptr才能运行起来。

我最喜欢的一种释放宏:

#define Safdelete(ptr)  if(nullptr != ptr) { delete ptr; ptr = nullptr;}

现在我终于明白了,真正的用意,是为了防止多次 delete ptr;  而不是 delete 0; 哇塞。

ok , 现在总结一下。

首先,我们不能把生存时间比auto_ptr变量长的指针变量作为auto_ptr构造函数的参数,所以要限制指针变量为右值,不能把 为左值的auto_ptr变量赋值给其他的auto_ptr变量.

其次,auto_ptr 不能使用数组指针,例如:std::auto_ptr<char>(new char[100]);

因此,我决定,修改一下auto_ptr的源码,更易于使用。

夜已深,我要去吃个面,去网吧玩几把英雄联盟了。以后再写修改版的auto_ptr. i just a 程序员 !!!


原创粉丝点击