美文网首页
智能指针和垃圾回收

智能指针和垃圾回收

作者: ColdWave | 来源:发表于2018-08-25 21:51 被阅读0次

堆内存管理:智能指针与垃圾回收

显式内存管理

  • 野指针
  • 重复释放
  • 内存泄漏

C++11 的智能指针

  • unique_ptr
  • shared_ptr
  • weak_ptr
#include <iostream>
#include <memory>

int main()
{
    std::unique_ptr<int> up1(new int(11));
//    unique_ptr<int> up2 = up1; // error, 不能复制

    std::cout << *up1 << std::endl; // 11

    std::unique_ptr<int> up3 = std::move(up1); // up3 是数据唯一的 unique_ptr 智能指针

    std::cout << *up3 << std::endl; // 11
    std::cout << *up1 << std::endl; // error, 但偶尔可能输出正确的值,但是不安全

    up3.reset(); // 显式释放内存
    up1.reset(); // 不会导致运行时错误

    std::cout << *up3 << std::endl; // 运行时错误

    std::shared_ptr<int> sp1(new int(22));
    std::shared_ptr<int> sp2 = sp1;

    std::cout << *sp1 << std::endl; // 22
    std::cout << *sp2 << std::endl; // 22

    sp1.reset();
    std::cout << *sp2 << std::endl; // 22

    return 0;
}
  • weak_ptr 可以指向 shared_ptr 指针指向的内存对象,却并不拥有该内存。而使用 weak_ptr 的 lock 成员,可以返回其指向内存的一个 shared_ptr 对象,且在所指向内存已经失效时,返回空值。
#include <iostream>
#include <memory>

void check(std::weak_ptr<int> &wp)
{
    std::shared_ptr<int> sp = wp.lock();
    if (sp != nullptr) {
        std::cout << "Still " << *sp << std::endl;
    } else {
        std::cout << "Pointer is invalid." << std::endl;
    }
}

int main()
{
    std::shared_ptr<int> sp1(new int(22));
    std::shared_ptr<int> sp2 = sp1;
    std::weak_ptr<int> wp = sp1;

    std::cout << *sp1 << std::endl; // 22
    std::cout << *sp2 << std::endl; // 22

    check(wp); // Still 22

    sp1.reset();
    std::cout << *sp2 << std::endl; // 22
    check(wp); // Still 22

    sp2.reset();
    check(wp); // Pointer is invalid.

    return 0;
}

智能指针虽然好,但是还是要显式使用。

垃圾回收的分类

Garbage Collection

Language Support
C++ 部分
Java 支持
Python 支持
C 不支持
C# 支持
Ruby 支持
PHP 支持
Perl 支持
Hashkell 支持
Pascal 不支持

垃圾回收主要分为两类:

  • 基于引用计数(reference counting grabage collector)的垃圾回收器

    引用计数必须存在的问题: retain cycle

  • 基于跟踪处理(tracing grabage collector)的垃圾回收器

    • 标记——清除(Mark——Sweep)

      算法分为两个过程:首先将程序中正在使用的对象视为“根对象”,从根对象开始查找它们所引用的堆空间,并在这些堆空间上做标记。当标记结束后,所有被标记的对象就是可达对象(Reachable Object)或活对象(Live Object),而没有被标记的对象被认为是垃圾,在第二步的清扫(Sweep)阶段会被回收掉。

      特点式活的对象不会被移动,但是其存在大量的内存碎片问题。

    • 标记——整理(Mark——Compact)

      这种算法标记的方法和标记——清除方法一样,但是标记完后,不再便利所有对象清扫垃圾了,而是将活的对象向“左”靠齐,这就解决了碎片问题。

    • 标记——拷贝(Mark——Copy)

      将堆空间分为两部分:From和To。刚开始系统从 From 堆空间里分配内内存,当 From 分配满的时候系统就开始垃圾回收:从From堆空间里找出所有活的对象,拷贝到To的堆空间里。这样一来,From的堆空间里就全剩下垃圾了。而对象拷贝到To里之后,在To里式排列紧凑的。接下来需要将From和To交换角色,接着从新的From里开始分配。

      该算法堆的利用率只有一半,而且也需要移动活的对象。此外,从某种意义上讲,该算法其实是标记——整理算法的一种实现而已。

C++ 与垃圾回收

  • Boehm 已经支持 标记——清除 方法的垃圾回收,但是可移植性不好
  • 为了解决GC中安全性和可移植性的问题,在2007年,HP 的 Hans-J.Boehm 和 Symantec 的 Mike Spertus 共同向 C++ 委员会提交了一个关于 C++ 中垃圾回收的提案。该提案通过添加 gc_forbidden, gc_relaxed, gc_required, gc_safe, gc_strict 等关键字来支持 C++ 语言中的垃圾回收。由于给特定过于复杂,并且存在问题,后来被标准删除了。所以 Boehm 和 Spertus 对初稿进行了简化,仅仅保留了支持GC的最基本部分,即通过语言的约束,来保证安全的GC。这也是我们看到的C++11标准中的“最小垃圾回收的支持”的历史由来。
  • 要保证安全的GC,首先必须知道C/C++语言中什么样的行为可能导致GC出现不安全的情况。简单地说,不安全源自于C/C++语言对指针的“放纵”,即允许过分灵活的使用。
int main()
{
    int *p = new int;
    p += 10; // 移动指针,可能导致 GC
    p -= 10; // 回收原来指向的内存
    *p = 10; // 再次使用原本相同的指针可能无效
}
int main()
{
    int *p = new int;
    int *q = (int *)(reinterpret_cast<long long>(p) ^ 2012); // q 隐藏了 p

    // 做了一些其他工作,垃圾回收器可能已经回收了 p 指向对象
    q = (int *)(reinterpret_cast<long long>(q) ^ 2012); // 这里 q == p
    *q = 10;
    /*
     * 先将 p 隐藏,然后恢复。
     * 在隐藏 p 后,很有可能出发 GC,导致恢复 p 后仍为非法。
    */
}

C++11与最小垃圾回收支持

C++11 新标准为了做到最小的垃圾回收的支持,首先对“安全”的指针进行了定义,或者使用C++11中的术语说,安全派生(safely derived)的指针。安全派生的指针指向由 new 分配的对象或其子对象的指针。安全派生指针的操作包括:

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

通过 get_pointer_safety 函数查询编译器是否支持 “安全派生” 特性。

pointer_safety get_pointer_safety() noexcept
// 返回值:
- pointer_safety::strict —— 编译器支持最小垃圾回收及安全派生指针等相关概念
- pointer_safety::relax —— 编译器不支持
— pointer_safety::preferred —— 编译器不支持

如果程序中出现了指针不安全使用的状况,C++11 运行程序员通过一些API来通知GC不得回收该内存。

void declare_reachable(void *p);
template <class T> T* undeclare_rechable(T *p) noexcept;

declare_reachable 显式的通知 GC 某一个对象被认为是可达的,即使它所有指针都对回收器不可见。

undeclare_rechable 则可以取消这种可达声明。

#include <iostream>
#include <memory>

int main()
{
    int *p = new int;
    std::declare_reachable(p); // 在 p 被 不安全派生 前声明,这样 GC 不会回收

    int *q = (int *)((long long)p ^ 2012);

    // 解除可达声明
    q = undeclare_reachable<int>((int *)((long long)q ^ 2012));
    *q = 10;

    return 0;
}
  • declare_no_pointers
  • undeclare_no_pointers

告诉编译器该内存区域不存在有效的指针。

void declare_no_pointers(char *p, size_t n) noexcept;
void undeclare_no_pointers(char *p, size_t n) noexcept;

垃圾回收的兼容性

GC 必然会破坏向后兼容。因此,我们必须限制指针的使用或者使用 declare_reachable/undeclare_reachable, declare_no_pointers/undeclare_no_pointers 来让一些不安全的指针使用免于垃圾回收的检查。因此想让老代码毫不费力地使用垃圾回收,现实情况对大多数代码不太可能。

此外, C++11 标准对指针垃圾回收的支持仅限于 new 操作符分配的内存;
malloc 分配的内存会被总认为是 可达的,即无论何时垃圾回收器都不予回收。

相关文章

  • 智能指针和垃圾回收

    堆内存管理:智能指针与垃圾回收 显式内存管理 野指针 重复释放 内存泄漏 C++11 的智能指针 unique_p...

  • 垃圾回收

    垃圾回收一共分两种:栈中的垃圾回收和堆中的垃圾回收 栈中的垃圾回收 JS 引擎有一个记录当前执行状态的指针,称为 ...

  • java基础知识,垃圾回收

    知识要点: 垃圾回收要点知识 垃圾回收算法 垃圾回收器 垃圾回收机制 GC所关心的东西:“这块数据是不是一个指针”...

  • 你需要的优秀的毕业设计小程序都在这

    1. 智能垃圾回收小程序 智能垃圾回收小程序,支持拍照识别、语音录入识别,包含垃圾分类题库测试。 云开发版:使用小...

  • Java 垃圾回收算法

    一、概述 说到垃圾回收,我们必须要知道什么是垃圾?为什么要回收? 什么是垃圾:垃圾是在程序运行中没有任何指针指向的...

  • 内存回收(GC)

    首先我们要先明白什么是垃圾?就是没有任何指针指向的对象,可以成为可回收垃圾; 其次为什么需要垃圾回收?因为内存持续...

  • GC垃圾回收机制

    GC 是什么? GC 即 垃圾回收(Garbage collection )垃圾是指:在运行的程序中没有被任何指针...

  • [c++11]智能指针学习笔记

    C#、Java、python和go等语言中都有垃圾自动回收机制,在对象失去引用的时候自动回收,而且基本上没有指针的...

  • 01垃圾回收机制

    垃圾回收(Garbage Collection,GC) 垃圾回收就是释放垃圾占用的空间 内存的动态分配和垃圾回收,...

  • JVM常见垃圾回收器介绍

    垃圾回收器简介 在新生代和老年代进行垃圾回收的时候,都是要用垃圾回收器进行回收的,不同的区域用不同的垃圾回收器。分...

网友评论

      本文标题:智能指针和垃圾回收

      本文链接:https://www.haomeiwen.com/subject/whqniftx.html