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;}