• C++堆内存管理


    C++堆内存管理

     

    1. auto_ptr的缺陷

    在很早的C++98之前,C++用"auto_ptr"智能指针来管理堆分配的内存,它的使用非常简单:

    auto_ptr<int> ap(new int(1024));

    即将new操作返回的指针作为auto_ptr的初始值,不用调用delete即可实现堆内存的自动释放(如析构的时候)。

    由于auto_ptr本身存在一些问题,它在C++11中被抛弃了。例如

    1. auto_ptr不能共享指向对象的所有权,因为auto_ptr不含有赋值语义,而是转移语义,即对象控制权的转移。
    2. auto_ptr不能指向数组。因为其实现中调用的是delete而非delete[]。
    3. auto_ptr不能作为容器类的元素,因为不满足容器的要求,复制或赋值后,两个对象必须具有相同值。

    取而代之的是unique_ptr、share_ptr、weak_ptr等智能指针来回收有堆分配的对象。

    1. unique_ptr指针

    unique_ptr顾名思义即无法复制的智能指针,如下:

    unique_ptr<int> var_ptr1(new int(11));

    它不能与其他的unique_ptr指针对象共享所指向的内存,如下的表达式是不允许的:

    unique_ptr<int> var_ptr2= var_ptr1;

    但是可以通过:

    unique_ptr<int> var_ptr2=move(var_ptr1);

    将var_ptr1的所有权转移给var_ptr2。

    这里unique_ptr和auto_ptr一样不能共享指向对象的所有权。简单的情况下使用unique_ptrk可以直接代替auto_ptr指针。为了解决auto_ptr不能共享对象内存是所有权的这一问题,C++11引入了share_ptr。

     

    1. share_ptr指针

    share_ptr允许多个智能指针共享同一对象由堆所分配的内存,

    share_ptr<int> var_ptr3(new int(12));

    share_ptr<int> var_ptr4= var_ptr3;

    在var_ptr3将内存释放后,

    var_ptr3.reset();

    即显式的调用var_ptr3.reset()后,var_ptr4所指向的为原来var_ptr3所分配的内存不受任何影响,而只是将指向这块内存的引用计数减一,如果引用计数减到0后,说明这块内存的所有者都不需要这块内存了,share_ptr才真正释放堆内存空间。

     

    但是如何知道一个share_ptr智能指针的引用计数减为0了,也就是说如何判断share_ptr的有效性呢?weak_ptr智能智能的lock成员可以帮上忙。

     

    1. share_ptr指针

    weak_ptr操作也很简单,如下:

    share_ptr<int> var_ptr3(new int(12));

    share_ptr<int> var_ptr4= var_ptr3;

    weak_ptr<int> w_ptr= var_ptr3;

    share_ptr<int> ptr = w_ptr.lock();

    通过ptr是否为空即可判断share_ptr的有效性。

    总而言之,unique_ptr在一般的情况下可以代替auto_ptr指针,而share_ptr和weak_ptr则可以用在需要引用计数的地方。

    1. 垃圾回收机制

    智能指针可以有效的帮助程序员管理堆内存,但是需要显式的声明智能指针,但是向其他的一些语言如JAVA和python则完全不需要考虑回收指针类型,因为他们支持垃圾回收机制,而C++目前只支持最小垃圾回收机制。

    垃圾回收的方法:

    基于引用计数

    引用计数的方法比较简单,在系统分配堆内存给一个对象后引用计数加一,当某一个对象释放堆内存后引用计数减一,直到引用计数为0,被分配给对象的内存则被回收。

    优点:不会造成程序暂停,不会对系统缓存和交换空间造成冲击。

    缺点:不能解决"环形引用"的问题,计数开销不小。

     

    基于跟踪处理

    基于跟踪处理的垃圾回收机制的基本思想是产生跟踪对象的关系图。

    1. 标记-清除

      从根对象开始查找它们所引用的堆空间,并在这些堆空间上做标记,当标记结束后,所有的被标记的对象为可达对象或活对象,没有被标记的则被认为是垃圾,然后这些垃圾被回收。

      缺点:活对象由于不会被移动则会产生大量的内存碎片。

    2. 标记-整理

      此方法和和标记清除的方法一样,但是在标记完之后会将可达对象也就是活对象向左靠齐,由此解决了内存水平地问题。

    3. 标记-拷贝

      此算法其实是标记整理的另外一种实现方式,它也有一些问题就是对的利用率只有一半,也需要移动活对象。

      1. C++最下垃圾回收机制

    C++目前只支持最小垃圾回收机制,这其中最主要的原因是C/C++对指针操作的灵活性,当然这也是C/C++的特点和优势,因为这是的程序员可以直接操作内存,这也是为什么C++程序更加高效的原因之一,但是正是由于这个特点和优势使得C/C++要实现内存垃圾回收会存现一些"不安全的"状况,这导致了C++到目前为止还没有完全支持垃圾回收。

    为什么说C++对指针的操作会导致垃圾回收时产生不安全的因素呢?看下式:

    Int *p =new int;

    P+=10;

    p-=10;

    *p=10;

    在上面的操作中我们可以看出,在指针移动后,如果垃圾回收器被设计为这个时候回收p原来指向的内存,则会导致p再次移动回原来位置的时候指向了一个无效的地址(指针已经被回收了);后面的*p=10对这个无效的指针进行操作可想而知后果是什么,这就导致设计垃圾回收器的时候进入了一个两难的境地,如何设计才能保证内存垃圾被正确的回收,这就给C++的垃圾回收器的设计带来挑战,到底是从编译器端着手解决这些问题,还是其他方式,现在还没有定论。

    正是由于这些安全的问题,导致C++目前为止只支持最小垃圾回收机制。最下垃圾回收机制针对提出了安全派生指针,它是指由new分配的对象或其子对象的指针。

    1. 在解引用基础上的引用,比如:&*p。
    2. 定义明确的指针操作,比如:p+1;
    3. 定义明确的指针转换,比如:static_cast<void>(p).
    4. 指针和整型之间的reinterpret_cast,比如:reinterpret_cast<intptr_t>(p)

       

      【查看编译器是否支持这个特性】,可以通过下式:

      Point_safety get_pointer_safty() noexcept

      如果它返回point_safety类型的值,如果值为pointer_safety::strict, 则表明编译器支持最小垃圾回收及安全派生指针,如果返回为pointer_safety::relax或pointer_safety::preferred则表明编译器不支持。

      【通知垃圾回收器不得回收某资源】可通过下面的接口实现。

      Void declare_reachable(void * p);

      即通知垃圾回收器某一资源为可到达,这样垃圾回收器就不会回收该资源。

      Template <class T> T *undeclare_reachable(T *p) noexcept;

      将资源的可达声明取消,垃圾回收器可见该资源,则可以回收该资源。

       

      【对大片连续内存的操作】有以下API实现

      Void declare_no_pointers(char *p,size_t n) noexcept;

      这个函数可以告诉垃圾回收器*p指向的n大小的内存不存在有效的指针。

      Void undeclare_no_pointers(char *p,size_t n) noexcept;

      这个函数可以告诉垃圾回收器*p指向的n大小的内存存在有效的指针。

       

      1. C++最小垃圾回收机制的支持

    C++11标准中针对垃圾回收的支持仅限于new操作符分配的内存,而用malloc分配内存则不予回收,程序员还是需要自己控制堆内存的回收。

  • 相关阅读:
    消息中间件
    线程以及多线程
    锁以及分布式锁
    并发以及高并发
    SpringBoot + SpringCloud学习踩坑实记
    公众号笔记: 2018年12月
    浅谈final关键字的用法
    浅谈static关键字的四种用法
    Linux常用的一些命令
    HTTPS
  • 原文地址:https://www.cnblogs.com/spoorer/p/7155144.html
Copyright © 2020-2023  润新知