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;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。