C++ 内存问题 auto_ptr

c/c++内存使用

这里写图片描述

这里写图片描述

内存问题

  • 野指针

一些内存已经被释放了,之前指向它的指针仍然在被利用。这些内存有可能在运行时被系统重新分配给其他程序使用,从而导致无法预测的错误

  • 重复释放

释放已经释放了的内存,或者释放配重新分配过的内存
delete/free后,应该接着设置为NULL;

  • 内存泄漏

不再使用的内存单元如果没有被释放就会导致内存泄漏。如果程序不断地重复进行这类操作,将会导致内存占用剧增

char* mal = (char*)malloc(100);
int *pi = (int*)mal;
int *pi2 = (int*)(mal + sizeof(int));
*pi = 0x12345678;
*pi2 = 0x00010203;
printf("%x %x\n", *pi,*pi2);
delete mal; //delete pi; 
// delete pi2; // 不正确,malloc要集体delete,不能释放malloc分配的一部分空间

auto_ptr

智能指针通过一个模板类型“auto_ptr”来实现。auto_ptr以对象的方式管理堆分配的内存,并在适当的时间(比如析构),释放所获得的堆内存,不需要显示的调用delete。

auto_ptr实现关键点

  • 利用特点“栈上对象在离开作用范围时会自动析构”

  • 对于动态分配的内存,其作用范围是程序员手动控制的,这给程序员带来了方便但也不可避免疏忽造成的内存泄漏,毕竟只有编译器是最可靠的。

  • auto_ptr通过在栈上构建一个对象a,对象a中wrap了动态分配内存的指针p,所有对指针p的操作都转为对对象a的操作。而在a的析构函数中会自动释放p的空间,而该析构函数是编译器自动调用的,无需程序员操心。

C++ auto_ptr智能指针的用法
http://blog.csdn.net/monkey_d_meng/article/details/5901392

经典的内存泄漏场景

// 示例1(a):原始代码    
void f()  
{  
    T* pt( new T );  
    /*...更多的代码...*/  
    delete pt;  
}  
  • 我们大多数人每天写类似的代码。如果f()函数只有三行并且不会有任何意外,这么做可能挺好的。

  • 但是如果f()从不执行delete语句,或者是由于过早的返回,或者是由于执行函数体时抛出了异常,那么这个被分配的对象就没有被删除,从而我们产生了一个经典的内存泄漏。

使用 auto_ptr改进

void f()  
{  
    auto_ptr<T> pt( new T );  
    /*...更多的代码...*/  
} // 当pt出了作用域时析构函数被调用,从而对象被自动删除 

c++ primer 第六版 第16章
这里写图片描述

auto_ptr的使用

// 现在,我们有了一个分配好的对象  
int* pt1 = new int;  

// 将所有权传给了一个auto_ptr对象  
auto_ptr<int> pt2(pt1);  

// 使用auto_ptr就像我们以前使用简单指针一样,  
*pt2 = 12;          // 就像*pt1 = 12  

cout << *pt1 << endl;   // 12

// 用get()来获得指针的值  
pt1 = pt2.get();

// 用release()来撤销所有权,并没有释放内存 
int *pt3 = pt2.release();  

// ptr2现在是空的,ptr2.get() 是空指针
if( pt2.get() == nullptr)
{
    cout << "nullptr" << endl;  // nullptr
}

cout << *pt3 << endl; // 12

// 目前 pt1 和 pt3都指向一样的内存空间,不能够重复delete
delete pt3;  
pt3 = NULL;
pt1 = NULL;

auto_ptr的注意事项

见 《c++primer第六版 第16章》

auto_ptr<string> ps(new string("a new auto_ptr"));
auto_ptr<string> vocation;
vocation = ps;

如果ps,vocation是普通指针,那么两个指针指向同一个string对象,在删除是可能同时两次delete, 一次是ps过期时,一次是vocation过期时
避免此问题, 方法多种, 如以下的几种

  • 定义赋值运算符,使之进行深复制。这样两个指针将指向不同的对象

  • 建立所有权(ownership)概念,对于特定的对象,只能有一个智能指针拥有它,这样只有拥有对象的智能指针的构造函数会删除该对象。然后,让赋值操作转让所有权,这就是用于auto_ptr和unique_ptr的策略,但unique_ptr的策略更严格。

  • 创建智能更高的指针,跟踪引用特定对象的智能指针数。这成为引用计数(reference counting)。shared_ptr 采取此策略。

代码实验

auto_ptr<string> ps(new string("a new auto_ptr"));
auto_ptr<string> vocation;
// 此时 ps 是 auto_ptr, vocation 是empty

vocation = ps; // 这句转让权,vocatio是auto_ptr, ps是empty

这里写图片描述

这里写图片描述

使用unique_ptr的情况,所有权是不可剥夺的,直接语法错误,如下所示
(这个可能需要看编译器)
这里写图片描述

参考
http://blog.csdn.net/fayery/article/details/25913445

根据auto_ptr的源码

template<class T>  
inline T* auto_ptr<T>::release()  
{  
    T *oldPointee = pointee;  
    pointee = 0;  
    return oldPointee;  
}  

template<class T>  
inline void auto_ptr<T>::reset(T *p)  
{  
    if (pointee != p)  
    {  
        delete pointee;  
        pointee = p;  
    }  
}  

template<class T>  
    template<class U>  
    inline auto_ptr<T>::operator =(auto_ptr<U> &rhs)  
    {  
        if(this != &rhs)  
            reset(rhs.release());  
        return *this;  
    }  
template<class T>  
inline T& auto_ptr<T>::operator *() const  
{  
    return *pointee;  
}  

template<class T>  
inline T* auto_ptr<T>::operator ->() const  
{  
    return pointee;  
} 

重载了=运算符,会出现所有权的转让

内存碎片

转载
Greenday
C++内存泄漏和内存碎片的产生及避免策略
http://blog.csdn.net/zzucsliang/article/details/43876173

什么是内存碎片

内存碎片:描述一个系统中所有的不可用的空闲内存;这些资源之所以仍然未被使用,是因为负责分配内存的分配器使这些内存无法使用。这一问题通常都会发生,原因在于空闲内存以小而不连续方式出现在不同的位置。由于分配方法决定内存碎片是否是一个问题,因此内存分配器在保证空闲资源可用性方面扮演着重要的角色。

内存碎片产生的原因

原因在与空闲内存以小而不连续的方式出现在不同的位置(内存分配较小,并且分配的这些小的内存生存周期又较长,反复申请后将产生内存碎片的出现)。内存分配程序浪费内存的基本方式有三种:即额外开销、内部碎片以及外部碎片

  • 内存分配程序需要遵循一些基本的内存分配规则。例如,所有的内存分配必须起始于可被 4、8 或 16 整除(视处理器体系结构而定)的地址。内存分配程序把仅仅预定大小的内存块分配给客户,可能还有其它原因。当某个客户请求一个 43 字节的内存块时,它可能会获得 44字节、48字节 甚至更多的字节。由所需大小四舍五入而产生的多余空间就叫内部碎片

  • 外部碎片的产生是当已分配内存块之间出现未被使用的差额时,就会产生外部碎片。例如,一个应用程序分配三个连续的内存块,然后使中间的一个内存块空闲。内存分配程序可以重新使用中间内存块供将来进行分配,但不太可能分配的块正好与全部空闲内存一样大。倘若在运行期间,内存分配程序不改变其实现法与四舍五入策略,则额外开销和内部碎片在整个系统寿命期间保持不变。虽然额外开销和内部碎片会浪费内存,因此是不可取的,但外部碎片才是嵌入系统开发人员真正的敌人,造成系统失效的正是分配问题。

如何避免内存碎片的产生

1 少用动态内存分配的函数(尽量使用栈空间)
2 分配内存和释放的内存尽量在同一个函数中
3 尽量一次性申请较大的内存2的指数次幂大小的内存空间,而不要反复申请小内存(少进行内存的分割)
4 使用内存池来减少使用堆内存引起的内存碎片
5 尽可能少地申请空间。
6 尽量少使用堆上的内存空间~
7 做内存池,也就是自己一次申请一块足够大的空间,然后自己来管理,用于大量频繁地new/delete操作。

内存管理系统将能够急时合并相邻空闲内存块,得到更大的空闲内存。这样并不会导致内存碎片的出现。即使相邻空间不空闲,这样产生的碎片还是比较少的,但是对于游戏(运行时间较长)或者手机(内存较小)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值