C++学习笔记07--动态内存管理

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智能指针

【C++】智能指针详解_Billy12138的博客-CSDN博客_智能指针参考资料:《C++ Primer中文版 第五版》 我们知道除了静态内存和栈内存外,每个程序还有一个内存池,这部分内存被称为自由空间或者堆。程序用堆来存储动态分配的对象即那些在程序运行时分配的对象,当动态对象不再使用时,我们的代码必须显式的销毁它们。在C++中,动态内存的管理是用一对运算符完成的:new和delete,new:在动态内存中为对象分配一块空间并返回一个指向该对象的指针,delet...https://blog.csdn.net/flowing_wind/article/details/81301001

使用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

-开辟单独线程(不断检测堆区的内存)消耗资源

-不能对对象的生命周期精确的控制

持续更新............................

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值