01new/delete
堆vs栈
栈内存特点: 更好的全局性,对象自动销毁
堆内存的特点: 运行期动态扩展,需要显示释放
int x; x在栈区
x=2;
cout<<x<<endl;
int *y=new int(2); y开辟在堆区 开辟具有4个字节的内存 将2存入内存 返回这段内存的首地址
cout<<*y<<endl; 采用解引用来读取y
delete y; 销毁y
int *fun()
{
int *res=new int(4); 合法 res开辟在堆区 作为函数返回值未销毁 y指向res
return res;
}
int *fun2()
{
int res2=3; 不合法 res2开辟在栈区 作为函数返回值后已销毁 z指向res2及指向一个已经销毁的地址
return &res;
}
int main()
{
int *y=fun();
cout<<*y<<endl;
delete y;
int *z=fun2(); error
cout<<*z<<endl;
}
new
C++通常需要new和delete来构造和销毁对象
new:构造对象 先分配内存 再构造对象
delete:对象销毁 先销毁对象 再释放内存
new的形式:
1.构造单一的对象/对象数组
int main()
{
int x;
int *y=new int;
delete y;
int *z=new int[5]; 开辟20个字节的内存空间 保存5个int的对象 返回首个int的地址
int *a=new int[3]{1,2,3};
cout<<a[2]<<endl;
delete []z;
delete []a;
}
nothrow new 不抛异常的new 需要头文件#include<new>
#include<new>
int *y=new(std::nothrow)int[5]{};
if(y==nullptr){....} 如果y经过一系列操作后指向空了 不让编译器报异常从而停止程序
delete []y
placement new 已有内存 只构造对象
char ch[sizeof(int)];
int *y=new(ch)int(4); 在ch包含的内存上,创建对象 此处是栈内存
cout<<*y<<endl;
new auto
int *y=new auto(3); 可以推导出int类型
int *x=new auto ; error
delete
delete常见用法:
-销毁单一对象或者数组 delete pty ; delete[ ] ptr
-placement delete 只销毁对象 不释放内存
Notes:
--根据分配的是单一对象还是数组,采用相应的delete
--delete nullptr
int *x=nullptr;int *y=0; delete x;delete y; delete 什么也不做
--不能delete一个非new返回的内存 int x; delete x; error
--同一块内存不能delete多次
int *ptr=new int[5];
int *ptr2=ptr+1;
delete []ptr2 error
int *x=new int(4);
delete x;
cout<<*x<<endl;
delete x error
int *x=new int(4);
delete x;
x=nullptr;
delete x 合法
02智能指针
使用new/delete的问题: 内存所有权不清晰,容易产生不销毁,多销毁的情况----
解决方法--智能指针
C++ 智能指针底层是采用引用计数的方式实现的。简单的理解,智能指针在申请堆内存空间的同时,会为其配备一个整形值(初始值为 1),每当有新对象使用此堆内存时,该整形值 +1;反之,每当使用此堆内存的对象被释放时,该整形值减 1。当堆空间对应的整形值为 0 时,即表明不再有对象使用它,该堆空间就会被释放掉。
int *fun()
{
int *res=new int(100);
return res;
}
int main()
{
int *y=fun(); main 和fun谁拥有res的所有权 谁来释放堆区内存不清晰
}
int *fun2()
{
static int res=200; 静态局部变量既不在堆区也不再栈区
return &res;
}
int main()
{
int *y=fun(); 通常程序执行完 系统自动销毁res
}
shared_ptr 共享指针
auto_ptr C++98和C++03引入
shared_ptr unique_ptr weak_ptr C++11引入
shared_ptr-----基于引用计数的内存共享解决方案 包含头文件#include<memory>
多个 shared_ptr 智能指针可以共同使用同一块堆内存。并且,由于该类型智能指针在实现上采用的是引用计数机制,即便有一个 shared_ptr 指针放弃了堆内存的“使用权”(引用计数减 1),也不会影响其他指向同一堆内存的 shared_ptr 指针(只有引用计数为 0 时,堆内存才会被自动释放)
1.基本用法
#include<memory>
#include<iostream>
int main()
{
std::shared_ptr<int>x(new int(4)); 初始化
cout<<x.use_count()<<end; 引用计数 1
std::shared_ptr<int>y=x;
cout<<y.use_count()<<endl; 引用计数 2
y先销毁 后构造的先销毁 shared_prt是类模板 销毁由它的析构函数来释放内存
}
2.get()/reset()用法
std::shared_ptr<int>fun()
{
std::shared_ptr<int>res(new int (100));
return res;
}
void fun(int *ptr){....}
int main()
{
auto y=fun();
cout<<*y<<endl; 100
cout<<*(y.get())<<endl; 100 y.get为 int*
fun(y); error
fun(y.get()); get使智能指针变为普通指针
}
#include<iostream>
#include<memory>
std::shared_ptr<int>fun()
{
std::shared_ptr<int>res(new int (23));
return res;
}
int main()
{
auto y=fun();
y.reset(new(int(3))); 把原来指向的内存释放掉 关联新的内存
y.reset((int*)nullptr);不关联任何内存 nullptr为nullptr_t 故需要转化为int*
y.reset();
}
3.指定回收逻辑
void fun(int *ptr)
{
cout<<"fun in called"<<endl;
delete ptr;
}
int main()
{
std::shared_ptr<int>x(new int(3),fun); x指向的内存引用计数为0时,会调用fun作为deleter
而不是默认的deleter
}
4.std::make_shared (引入优化+构造对象)
std::shared_ptr<int>ptr(new int(3));
std::shared_ptr<int>ptr2=std::make_shared<int>(3);
auto ptr3=std::make_shared<int>(3);
make_shared引入了优化 将关联的内存与维护引用计数的内存放的尽可能的近 使程序读取一次缓存即可 优化了程序的性能.
5.shared_ptr<T[ ]> C++17引入 make_shared分配数组 c++20引入
C++17之前 数组指针需要指定回收逻辑,C++17引入了对数组的支持
std::shared_ptr<int[]>ptr(new int [5]);
auto ptr2=std::make_shared<int[]>(5);
6.shared_ptr<>管理的对象不要调用delete销毁
智能指针陷阱:
(1)不使用相同的内置指针值初始化(或reset)多个智能指针。
(2)不delete get()返回的指针
(3)不使用get()初始化或reset另一个智能指针
(4)如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效
(5)如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器
std::shared_ptr<int>x(new int(3));
delete x.get(); 将x指向的内存销毁,当程序执行完销毁x时,x此时为悬挂指针,调用析构函数,再次销毁指向的内存,故重复释放
std::shared_ptr<int>y(x); x和y共享一套引用计数和内存
std::shared_ptr<int>y(x.get()); error 重复释放 x y只共享指向的内存
int *ptr=new int(3);
std::shared_ptr<int>i(ptr);
std::shared_ptr<int>j(ptr);error重复释放
unique_ptr 独占内存
某个时刻只能有一个unique_ptr指向一个给定对象,由于一个unique_ptr拥有它指向的对象,因此unique_ptr不支持普通的拷贝或赋值操作。
1.基本用法
unique_ptr不支持复制 但是可以移动 也可为unique_ptr指定回收逻辑
std::unique_ptr<int>x(new int(3));
std::unique_ptr<int>y=x;error unique_ptr不支持复制 但是可以移动
std::unique_ptr<int>y=std::move(x); 将亡值x 将资源全部给y
std::cout<<y.get()<<std::endl; 0x550 一段内存地址
std::cout<<x.get()<<std::endl; 0
std::unique_ptr<int>fun()
{
auto res=std::make_unique<int>(4);
return res;
}
int main()
{
std::unique_ptr<int>x=fun(); res将所有权转让给x;
}
void fun(int *ptr)
{
std::cout<<"fun is called"<<endl;
delete ptr;
}
int main()
{
std::shared_ptr<int>x(new int(3),fun);
std::unique_ptr<int,decltype(&fun)>y(new int(3),fun); 需要decltype
}
weak_ptr --防止循环引用引入的智能指针
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向对象,对象还是会被释放。
可以和shared_ptr共享内存,但是不增加引用计数
struct Str
{
std::weak_ptr<Str>m_nei;
~Str()
{
cout<<"~Str()is called"<<endl;
}
}
int main()
{
std::shared_ptr<Str>x(new Str{});
std::shared_ptr<Str>y(new Str{});
x->m_nei=y;
y->m_nei=x; 循环引用 若m_nei是std::shared_ptr则不能释放x y
}
03动态内存相关问题
1.sizeof不会返回动态分配的内存大小
int *ptr=new int (3);
int *ptr=new int [3];
cout<<sizeof(ptr)<<endl; 8
不论是int(3)还是int[3]仅返回分配内存的首地址 不返回分配内存的大小
std::vector<int>x;
cout<<sizeof(x)<<endl; sizeof(x)在编译期完场 故sizeof只返首地址
2.使用分配器allocator来分配内存
std::allocator<int>al;
int *ptr=al.allocator(5); 只分配包含5个int的内存 不构造对象 包含了对齐内存的操作
al.deallocator(ptr,3) 释放内存
3.使用malloc/free来管理内存 C语言
//void * malloc(size_t size)
int *p1=malloc(4*sizeof(int));
int *p2=malloc(sizeof(int [4]));
int *p3=malloc(4*sizeof(*p3));;
.......
free (p1);
free (p2);
free (p3);
malloc只分配字节 只关注分配内存的大小 不构造对象 内存也不一定对齐
经典面试题: new 和malloc 的区别?
C++ new和malloc的区别_Linux猿的博客-CSDN博客_c++malloc
经典面试题之new和malloc的区别_Datou_Nie的博客-CSDN博客_new和malloc的区别
1.使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸,不能对齐内存.
2.new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现.)
-malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作
3.new做两件事:分配内存和调用类的构造函数,delete是:调用类的析构函数和释放内存。而malloc和free只是分配和释放内存。
-new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中
4.使用aligned_alloc来分配对齐内存 256/4=64个int
5.动态内存与异常安全(产生异常,也会释放内存)
void fun()
{
int *ptr=new int(3);
... 可能有异常导致内存不能释放 使程序终止
delete ptr;
}
void fun()
{
std::shared_ptr<int>(new int(3));
} 使用智能指针 保证异常下 也能正常释放内存
6.C++对垃圾回收的支持
Garbage collector support
-开辟单独线程(不断检测堆区的内存)消耗资源
-不能对对象的生命周期精确的控制
持续更新............................