乐趣区

关于c++:C11-动态内存管理

C++ 内存

动态内存、动态内存
  • 动态内存调配好后,程序运行过程中始终存在不会被开释,且一旦调配好,其内存大小就固定下来不能扭转,在编译和链接的阶段就会调配好。
  • 动态内存是程序运行过程中,依据程序的运行须要调配和开释,其大小可变。
堆、栈

堆和栈都是动态分配的,区别有两点:

  • 栈是由编译器调配与开释;堆是程序通过调用 malloc 或 new 调配,调用 free 或 delete 开释。
  • 栈是线性构造;堆是链表构造。
存储场景
  • 动态内存用来保留部分 static 对象、类 static 数据成员以及定义在任何函数之外的变量。
  • 栈内存用来保留定义在函数内的非 static 对象,存储在栈上,函数退出时,其占用内存被发出。

调配在动态内存或栈内存中的对象由编译器主动创立和销毁。对于栈对象,仅在其定义的程序块运行时才存在;static 对象在应用之前调配,在程序完结时销毁。

  • 堆保留通过调用 malloc 或 new 失去的内存,不再须要时要显示地调用 free 或 delete 来开释

堆内存容许程序员动静地申请所需空间,但也要求他们一旦不须要这些内存资源的时候就偿还他们。

内存相干谬误

在程序运行的过程中,经常出现段谬误、内存继续增大等因为显式内存治理导致的问题,次要演绎为以下几点:

  • 野指针:一些内存单元曾经开释,但之前指向它的指针还在应用。
  • 反复开释:程序试图开释曾经被开释过的内存单元。
  • 内存透露:没有开释不再应用的内存单元。
  • 缓冲区溢出:数组越界。
  • 不配对的 new[]/delete[]

针对上述问题中的 1~3,C++ 规范中提供了智能指针来解决。

智能指针

智能指针是基于 RAII(Resource Acquisition Is Initialization)机制实现的类(模板),具备指针的行为(重载了 operator* 与 operator-> 操作符)。当对象创立的时候,进行初始化;来到其作用域后,通过主动调用析构函数开释资源。

RAII (Resource Acquisition Is Initialization,资源获取就是初始化),是 C ++ 语言的一种治理资源、防止透露的习用法。C++ 规范保障任何状况下,已结构的对象最终会销毁,即它的析构函数最终会被调用。简略的说,RAII 的做法是应用一个对象,在其结构时获取资源,在对象生命期管制对资源的拜访使之始终保持无效,最初在对象析构的时候开释资源。

C++11 新规范提供的两种智能指针区别在于治理底层指针的形式:

  • shared_ptr 容许多个指针指向同一个对象;
  • unique_ptr 独占所指向的对象;
    规范库还定义了一个名为 weak_ptr 的随同类,它是一种弱援用,指向 shared_ptr 所治理的对象。

头文件:<memory>
命名空间为:std

unique_ptr

unique_ptr”惟一“领有其所指对象,同一时刻只能有一个 unique_ptr 指向给定对象(通过禁止拷贝语义、只有挪动语义 std::move() 来实现)。

  • unique_ptr 指针自身的生命周期:从 unique_ptr 指针创立时开始,直到来到作用域。来到作用域时,若其指向对象,则将其所指对象销毁(默认应用 delete 操作符,用户可指定其余操作)。
    unique_ptr 指针与其所指对象的关系:在智能指针生命周期内,能够扭转智能指针所指对象,如创立智能指针时通过构造函数指定、通过 reset 办法从新指定、通过 release 办法开释所有权、通过挪动语义转移所有权。
#include <iostream>
#include <memory>
#include <vector>
 
using namespace std;
 
struct Foo {Foo() {}
    ~Foo() {}
    void Print() { cout << "Foo" << endl;}
};
 
int main() {Foo* p1 = new Foo();
    unique_ptr<Foo> up1;       // up1==nullptr
//  up1 = p1;                  // 编译谬误,不反对这样赋值
    up1.reset(p1);             // 替换治理对象,并开释之前治理的对象
    p1 = nullptr;
 
//  unique_ptr<Foo> up2(up1);  // 编译谬误,不反对这样结构
    unique_ptr<Foo> up2(std::move(up1)); // up1 所有权转移到 up2。up1==nullptr
 
    up1.swap(up2);             // up2 与 up1 治理对象的指针替换。up2==nullptr
 
    if (up1) {                 // up1 != nullptr 
        up1->Print();          // unique_ptr 重载了 ->
        (*up1).Print();       // unique_ptr 重载了 *}
//  up2->Print();              // 谬误 up2 == nullptr, 必须先判断再调用
 
    p1 = up1.get();            // get() 返回所治理对象的指针, up1 持续持有其管理权
    p1 = up1.release();        // release() 返回治理对象的指针,并开释管理权,up1==nullptr
    delete p1;
 
    unique_ptr<Foo> up3(new Foo());
    up3.reset();                // 显示开释开释治理对象的内存,也能够这样做:up = nullptr;
    vector<unique_ptr<Foo>> v;
    unique_ptr<Foo> up4(new Foo());
//  v.push_back(up4);            // 编译谬误,不反对这样拷贝
    v.push_back(std::move(up4);  // 只能 up4 放弃对其所有权,通过 std::move() 将所有权转移到容器中
 
    return 0;
}

shared_ptr

shared_ptr 基于“援用计数”模型实现, 多个 shared_ptr 对象能够领有同一个动静对象,并保护了一个共享的援用计数。当最初一个指向该对象的 shared_ptr 被销毁或者 reset 时,会主动开释其所指的对象,回收动静资源。
销毁该对象时,应用默认的 delete/delete[] 表达式,或者是在结构 shared_ptr 时传入的自定义删除器(deleter),以实现个性化的资源开释动作。

#include <iostream>
#include <memory>
#include <vector>
#include <assert.h>
 
using namespace std;
 
struct Foo {int v;};
 
int main() {shared_ptr<Foo> sp1(new Foo{10});
    cout << sp1.unique() << endl;    // 1 以后 shared_ptr 惟一领有 Foo 管理权时,返回 true,否则返回 false
    cout << sp1.use_count() << endl; // 1 返回以后对象的援用计数
 
    shared_ptr<Foo> sp2(sp1);
    assert(sp1->v == sp2->v);        // sp1 与 sp2 独特领有 Foo 对象
    cout << sp2.unique() << endl;    // 0 false
    cout << sp2.use_count() << endl; // 2
   
    sp1.reset();                     // 开释对 Foo 的管理权,同时援用计数减 1
    assert(sp1 == nullptr);          // sp1 为空
    cout << sp1.unique() << endl;    // 0 不会抛出异样
    cout << sp1.use_count() << endl; // 0 不会抛出异样
    cout << sp1.get() << endl;       // 0 不会跑出异样
//  cout << sp1->v << endl;          // 执行谬误 sp1 为 nullptr 时,operator* 和 operator-> 都会导致未定义行为
 
    cout << sp2.unique() << endl;    // 1 true
    sp1.swap(sp2);                   // sp1 与 sp2 替换管理权, 及援用计数
    assert(sp2 == nullptr);
    cout << (*sp1).v << endl;        // 10 同 sp1->v 雷同
 
    vector<shared_ptr<Foo>> vec;
    vec.push_back(sp1);
    cout << sp1.use_count() << endl; // 2
 
    return 0;
// vector 先析构,外面存储的对象援用计数减 1,但不为 0,不开释对象
// sp1 后析构,援用计数减 1,变为 0,开释所指对象的内存资源
}
  • shared_ptr 造成循环援用
#include <iostream>
#include <memory>
 
using namespace std;
struct FooB;
struct FooA;
typedef std::shared_ptr<FooB> FooBSP;
typedef std::shared_ptr<FooA>    FooASP;
 
struct FooA {~FooA() {cout<< "FooA distroyed" << endl;}
    FooBSP sptr_foob;
};
struct FooB {~FooB() {cout<< "FooB distroyed" << endl;}
    FooASP sptr_fooa;
};
 
int main() {
    {FooBSP  FooB(new FooB());
        FooASP  FooA(new FooA());
        FooB->sptr_fooa = FooA;
        FooA->sptr_foob = FooB;
    } // FooB 和 FooA,互相援用,来到作用域时援用计数都为 1,造成内存泄露
 
    return 0;
}

weak_ptr

weak_ptr 是为了配合 shared_ptr 而引入的一种智能指针,它只可能通过 shared_ptr 或者 weak_ptr 来结构。
weak_ptr 是作为 shared_ptr 的”观察者“,并不批改 shared_ptr 所治理对象的援用计数。当 shared_ptr 销毁时,weak_ptr 会被设置为空,所以应用 weak_ptr 比底层指针的益处在于可能晓得所指对象是否无效。
weak_ptr 不具备一般指针的行为,因为没有重载 operator* 和 ->。所以当 weak_ptr 察看的对象存在,并且须要批改其内容时,须要晋升为 shared_ptr 来操作。

#include <iostream>
#include <memory>
 
using namespace std;
 
struct Foo{int v;};
typedef std::shared_ptr<Foo> FooSP;
typedef std::weak_ptr<Foo>   FooWP;
 
void update(FooWP &wp) {if (!wp.expired()) {// 查看被治理对象是否被删除,true 删除,false 没被删除;比 use_count()== 1 要快
        FooSP sp = wp.lock();    // 晋升为强援用
        if (sp != nullptr) {    // 若晋升失败,shared_ptr 为 nullptr,此例子不会失败
            sp->v *= 2;
            cout << sp.use_count() << endl; // 此时援用计数为 2}
    } // sp 删除,援用计数减 1
    wp.reset(); // 显示开释所有权,或者等来到作用域会主动开释} 
 
int main(void){FooSP sp(new Foo{10});
    update(sp);  // 对 sp 进行操作
    cout << sp->v << endl;  // 20
    return 0;
}
退出移动版