【C++】11.动态内存管理[深蓝学院C++第9章]
一.基础
1.1栈内存stack和堆内存heap
栈内存:更好的局部性、相关数据较为临近,栈帧出栈时对象自动销毁
堆内存:运行期动态扩展,需要显式释放

1.2显式内存的开辟与释放
在C++中通常使用new和delete来构造和销毁对象
1.3new
对象的构造步骤:
(1)分配内存与在所分配的内存上构造对象
(2)对象的销毁与内存回收
new的几种常见形式:
(1)构造单一对象/对象数组
(2)nothrow new,分配失败时不抛出异常
#include <new> int* y=new (std::nothrow) int[5];判断y是否为nullptr来判定内存分配是否正常
(3)placement new
已经有一块分配好的合法的内存,足够大,不管是栈内存还是堆内存,用于在其上构造对象
char ch[sizeof(int)];int* y= new (ch) int(4);//不再为y开新的内存,而是使用指定的ch的内存来构造“4”
(4)new auto,int *y=new auto[3];//将auto自动推导为int
new与对象对齐
struct alignas(256) Str();//为Str开辟的内存以256为单位,内存地址的结尾一定是0x xx00
1.4delete
delete的常见用法:
(1)销毁单一对象/对象数组,delete p和delete[] p
(2)placement delete,将对象销毁掉,但是不会将内存归还给系统
1.5new和delete注意事项
注意事项
(1)根据分配的是单一对象还是数组,采用相应的方式销毁
(2)delete nullptr,允许这样的操作
(3)不能delete一个非new开辟的内存,int x=0;delete &x;这样delete栈上的内存是不可以的
(4)同一块内存不能delete多次
调整系统自身的new/delete行为,略
二.智能指针
2.1问题来源
new和delete搭配的问题:内存所有权不清晰,容易产生不销毁,多销毁的情况。例如delete去销毁栈帧退出时已销毁的栈内存。
解决方案:智能指针
(1)auto_ptr,在C++17被删除
(2)shared_ptr/unique_ptr/weak_ptr
2.2shared_ptr
基于引用计数的共享内存解决方案,#include <memory>
引用计数,自动判断是否和其他对象共享内存,如果有、有几个?
如果为0时表示该内存已经没有继续被使用,则回收该内存
2.2.1基本用法
(1)std::shared_ptr<int> x(new int(3));
(2)std::shared_ptr<int> y=new int[3];
std::shared_ptr<int> z = y;
2.2.2reset和get方法
get返回的是T*,用于兼容之前拿普通指针的交互函数
std::shared_ptr<int> y = fun();
std::cout << *(y.get());
reset的作用:
(1)重新设置智能指针指向的内存,也就是接收一个新的T*指针,y.reset(new int[4]);
(2)x.reset();或者x.reset((int*)nullptr);//传入一个空指针,重置为空
2.2.3指定内存回收逻辑
std::shared_ptr<int> x(new int(3),fun);//fun是自定义的内存回收的回调函数
有时可用于阻止系统自动回收,以免回收静态内存
2.2.4std::make_shared
std::stared_ptr<int> x = std::make_stared<int>(3);
使用make_shared可以实现更好的局部性,避免造成catch-miss造成更大的开销
2.2.5支持数组
std::shared_ptr<int> x(new int[3]);
智能指针在回收内存时调用delete会出现数组内存回收隐患,
解决方法:
C++17之后:泛型时指定为数组,例如std::shared_ptr<int[]> x(new int[3]);
C++20之后:使用make_shared,例如auto x =std::make_shared<int[5]>();//auto x =std::make_shared<int[]>(5);
2.2.6注意事项
(1)shared_ptr管理的对象不要手动调用delete销毁,否则会造成多重delete的异常
(2)不要出现多个shared_ptr重复回收一片内存,如:
int* ptr = new int(3);
std::shared_ptr<int> x(ptr);
std::shared_ptr<int> y(ptr);
两个智能指针会对ptr回收两次,造成异常
2.3 unique_ptr
内存独占的智能指针
2.3.1 基本用法
std::unique_ptr<int> x(new int(3));
2.3.2移动
std::unique_ptr<int> x(new int(3));
std::unique_ptr<int> y = std::move(x);//将将亡值x占用的内存转给y
2.3.3指定回收逻辑
略
2.4 weak_ptr
防止循环引用而引入的智能指针
三.相关问题
3.1sizeof
sizeof不会返回动态分配的内存大小
3.2分配器
推荐使用分配器allocator来分配内存,可以借助内存池的性能优化
构造对象分为分配内存和在内存上构造对象两步,allocator可以做第一步
std::allocator<int> al;
int* ptr = al.allocate(3);//开辟内存,能够放下3个int
al.deallocate(ptr,3);//回收指定长度的内存
3.3malloc和free
来自C语言,malloc/free只能分配内存,而不能构造对象,主要关注分配多大的内存
分配多大的内存需要进行逻辑运算,有可能引入对齐问题
推荐使用allocator
3.4aligned_alloc
来自C语言,分配的内存可以保证对齐
推荐使用allocator
3.5动态内存与异常安全
问题:在回收内存之前出现异常,导致回收操作未被执行
解决方案:智能指针可以解决该问题,起码在栈帧退出时智能指针会去回收内存
3.6C++对垃圾回收的支持
严格依赖于编译器的具体实现,很少有人用。
略

浙公网安备 33010602011771号