【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++对垃圾回收的支持

严格依赖于编译器的具体实现,很少有人用。

 

posted @ 2023-03-02 20:09  啊原来是这样呀  阅读(37)  评论(0)    收藏  举报