动态内存管理
一、动态内存基础
1.1 栈内存 堆内存
- 栈内存的特点:更好的局部性,对象自动销毁 (stack)
- 堆内存的特点:运行期动态扩展,需要显式释放 (heap)
- 在 C++ 中通常使用 new 与 delete 来构造、销毁对象;可以动态扩展,分配在堆内存
- 即使在函数中 new 内存,其他地方仍然可以读取
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yG9vs5rF-1639836597353)(458d30508a72fa43e4458b95cfb13feb.png)]
#include <iostream>
int main()
{
int* t = new int(2);
std::cout << *t << std::endl;
delete y;
}
1.2 对象的构造
- 1、分配内存
- 2、在所分配的内存上构造对象
- 对象的销毁与之类似
1.3 new 的几种常见形式
1.3.1 构造单一对象 / 对象数组
#include <iostream>
int main()
{
int* y = new int[5]{ 1,2,3,4,5 };//开辟4*5 字节的空间 返回首地址
std::cout << y[2];
delete[] y; // 不能直接delete释放
}
1.3.2 nothrow new
对于两个占用内存中的内存片段或内存耗尽后,不足以分配内存,这时候new会抛出异常
这时候分配失败不会抛出异常,会指向nullptr
#include <iostream>
#include<new>
int main()
{
int* y = new (std::nothrow) int[5]{ 1,2,3,4,5 };//开辟4*5 字节的空间 返回首地址
if(y==nullptr)
{
//...
}
std::cout << y[2];
delete[] y;
}
1.3.3 placement new
只分配了内存,没构造对象,需要对其重新解释:
#include <iostream>
#include<new>
int main()
{
char ch[sizeof(int)];//开辟了char[4] 和 int 一样大
int * y = new (ch) int(4); //重新构造成int
std::cout << *y;//但是要注意,像这样使用栈内存可能会由于函数运行结束等被释放
}
1.3.4 new auto
#include <iostream>
#include<new>
int main()
{
int * y = new auto (4);
//int * y = new auto ; 不能这样写
}
1.4 new 与对象对齐
#include <iostream>
#include<new>
struct alignas(1024) Str{}; //规定对齐1024字节
int main()
{
Str* ptr = new Str(); //new能自动识别对齐
std::cout << ptr << std::endl;
}
1.5 delete常见用法
- 销毁单一对象 / 对象数组
delete or delete[] - placement delete 把对象销毁,但是空间保留,比如vector
析构函数中可能用到::TODO::
1.6 使用 new 与 delete 的注意事项
- 根据分配的是单一对象还是数组,采用相应的方式销毁 [ ]
- delete nullptr 不会执行操作
#include <iostream>
int main()
{
int * x = nullptr;
if(...)
{
//...
}
delete x;
}
- 不能 delete 一个非 new 返回的内存
这里只是销毁了数组,但是 y 任然存在,里面存放的地址不变
#include <iostream>
int main()
{
int* y = new int[5]{ 1,2,3,4,5 };//开辟4*5 字节的空间 返回首地址
std::cout << y[2];
delete[] y; // 不能直接delete释放
}
-
同一块内存不能 delete 多次
防止报错 就带 delete 后把指针指向 nullptr -
调整系统自身的 new / delete 行为
- 不要轻易使用
二、 智能指针
问题:
使用 new 与 delete 的问题:::内存所有权不清晰::,容易产生不销毁,多销毁的情况
#include <iostream>
int* fun()
{
int* res = new int[1];
return res;
}
int main()
{
int * x = fun();
}
2.1 C++ 的解决方案:智能指针
- auto_ptr ( 问题多,C++17 删除)
- shared_ptr / uniuqe_ptr / weak_ptr
2.2 shared_ptr——基于引用计数的共享内存解决方案
2.2.1基本用法
#include <iostream>
#include<memory>
int main()
{
int* y(new int(3));
//shared_ptr和普通指针用法一样,但不会内存泄漏
std::shared_ptr<int> x(new int(3)); //引用计数 = 1
//shared_ptr 维护了引用计数
std::shared_ptr<int> z = x; //引用计数 = 2
//程序结束后后 先销毁 z 后销毁 x(先构造 后销毁)
//直到引用计数为0才会销毁
std::cout << x.use_count();
std::cout << z.use_count();
}
2.2.2 reset / get 方法
#include <iostream>
#include<memory>
std::shared_ptr<int> fun()
{
std::shared_ptr<int> res(new int(100));
return res;
}
int main()
{
auto y = fun();
std::cout << *(y.get()) << std::endl;//y.get()返回 int*,比如用在其他函数需要接受指针而不是智能指针
std::cout << *y << std::endl;//和上一句一样
}
#include <iostream>
#include<memory>
std::shared_ptr<int> fun()
{
std::shared_ptr<int> res(new int(100));
return res;
}
int main()
{
auto y = fun();
y.reset(new int(3)); //引用计数为1的话 减1 释放原始资源 绑定新的地址,加1
//如果引用计数>1的话,绑定新的地址,不释放源内存
std::cout << *(y.get()) << std::endl;//y.get()返回 int*
y.reset()//使y指向nullptr
}
2.2.3 指定内存回收逻辑
#include <iostream>
#include<memory>
void fun(int* ptr)
{
delete ptr;
}
int main()
{
std::shared_ptr<int> x(new int(3), fun);
}
::此程序无法运行:: shared_ptr对象销毁时会调用delete,res存在栈上,无法用delete销毁
#include <iostream>
#include<memory>
std::shared_ptr<int> fun()
{
static int res = 3;
return std::shared_ptr<int>(&res);
}
int main()
{
auto y = fun();
}
解决方法
用dummy替换delete
#include <iostream>
#include<memory>
void dummy(int*){}
std::shared_ptr<int> fun()
{
static int res = 3;
return std::shared_ptr<int>(&res,dummy);
}
int main()
{
auto y = fun();
}
这样也是个回收逻辑,当不调用的时候把数据放在内存池,但不销毁,调用的时候取出,这样就可以提升速度
2.2.4 std::make_shared
#include <iostream>
#include<memory>
int main()
{
std::shared_ptr<int> ptr(new int (2));
std::shared_ptr<int> ptr = std::make_shared<int>(2);
auto ptr = std::make_shared<int>(2);
//三种方式一样 提升构造性能
}
2.2.5 支持数组( C++17 支持 shared_ptr<T[ ]> ; C++20 支持 make_shared 分配数组)
auto ptr = std::make_shared<int[]>(4);
std::shared_ptr<int> ptr(new int[4]);
2.2.6 注意: shared_ptr 管理的对象不要调用 delete
2.3 unique_ptr——独占内存的解决方案
2.3.4 基本用法
#include <iostream>
#include<memory>
int main()
{
//x独占这段资源
std::unique_ptr<int> x(new int(4));
//不能写 std::unique_ptr<int> y = x;无法复制。
std::unique_ptr<int> y = std::move(x);//构造将亡值,地址没变
}
2.3.5 unique_ptr 不支持复制,但可以移动
参考上面的
#include <iostream>
#include<memory>
std::unique_ptr<int> fun()
{
//auto res = std::make_unique<int>(3);
std::unique_ptr<int> res(new int(3));
return res;
}
int main()
{
std::unique_ptr<int> x = fun();//这里就不是拷贝了 而是move
}
2.3.6 为 unique_ptr 指定内存回收逻辑
::与share_ptr不同::
2.4 weak_ptr——防止循环引用而引入的智能指针
可以看到析构函数不正常
#include <iostream>
#include<memory>
struct Str
{
std::shared_ptr<Str> m_nei;
~Str()
{
std::cout << "~Str--\n";
}
};
int main()
{
std::shared_ptr<Str> x(new Str{});
std::shared_ptr<Str> y(new Str{});
x->m_nei = y;
y->m_nei = x;
std::cout<<y.use_count();
}
修改:weak_ptr 可以和share_ptr共享,::但是不会增加引用计数"::
#include <iostream>
#include<memory>
struct Str
{
std::weak_ptr<Str> m_nei;
~Str()
{
std::cout << "~Str--\n";
}
};
int main()
{
std::shared_ptr<Str> x(new Str{});
std::shared_ptr<Str> y(new Str{});
x->m_nei = y;
y->m_nei = x;
std::cout<<y.use_count();
}
2.4.1 基于 shared_ptr 构造
2.4.2 lock 方法 判断有效性
打印出cannot 因为 y销毁 lock返回nullptr
#include <iostream>
#include<memory>
struct Str
{
std::weak_ptr<Str> m_nei;
~Str()
{
std::cout << "~Str--\n";
}
};
int main()
{
std::shared_ptr<Str> x(new Str{});
{
std::shared_ptr<Str> y(new Str{});
x->m_nei = y;
}
if (auto ptr = x->m_nei.lock()) //返回;share_ptr
std::cout << "can\n";
else std::cout << "cannot \n";
}
三、动态内存
3.1 sizeof 不会返回动态分配的内存大小
只返会指针所占空间(64位机 -> 8)
#include <iostream>
#include<memory>
int main()
{
int* ptr = new int[3];
std::cout << sizeof(ptr);
}
3.2 使用分配器( allocator )来分配内存
#include <iostream>
#include<memory>
int main()
{
std::allocator<int> al;
int* ptr = al.allocate(3);//分配内存,但不初始化,比如给类分配,但不构造
}
3.3 使用 malloc / free 来管理内存
继承于c语言 只会分配内存,不会构造 (传入字节数)