动态内存

本文深入探讨了智能指针(shared_ptr, unique_ptr, weak_ptr)在C++中的应用,解析了它们如何帮助开发者避免内存泄漏、重复释放内存和使用已释放内存等问题。同时,文章还介绍了动态内存分配与释放的机制,包括new、delete、make_shared等关键字的正确使用,以及allocator类与new操作的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

  • 静态/全局
  • 用来保存局部static对象,类static数据成员以及定义在任何函数之外的变量。
  • 由编译器自动创建和销毁
  • static对象在使用之前分配,在程序结束时销毁
  • 栈内存
  • 用来保存定义在函数内的非static对象
  • 由编译器自动创建和销毁
  • 仅在其定义的程序块运行时才存在
  • 用堆来存储动态分配的对象——那些在程序运行时分配的对象。
  • 就是由malloc分配的内存块
  • 动态对象的生存期由程序来控制,也就是说,当动态对象不再使用时,我们的代码必须显式的销毁它们
  • 常量存储区
  • 用来存放常量,程序结束时自动释放
  • 自由存储区
  • new。与堆类似.。一般意义上,new都是由malloc为底层的,有时候自由存储区和堆并没有什么区别

动态内存与智能指针

  • 动态内存的管理是通过一对运算符来完成的:new和delete
  • 忘记释放内存,就会导致内存泄露
  • 提前释放内存,就会导致产生非法指针

  • 智能指针负责自动的释放所指向的对象
智能指针操作/意义
shared_ptr允许多个指针指向同一个对象
unique_ptr“独占”所指向的对象

  • 智能指针的通用操作
代码操作/意义
shared_ptr< type > sp ,unique_ptr< type > up空智能指针,可以指向类型为type的对象
p将p作为一个条件判断,if(p),若p指向一个对象,则true
*p解引用
p.get()返回p中保存的指针。要小心使用。若智能指针释放了其对象,返回的指针所指向的对象也就消失了
swap(p,q)/p.swap(q)交换p, q

  • shared_ptr 类
  • 类似vector,当我们创建智能指针时,必须指出指针可以指向的类型
shared_ptr < string > p1; //可以指向string
shared_ptr< list<int> > p2; //可以指向,int为元素的list
  • 解引用一个智能指针返回它指向的对象。

shared_ptr独有的操作

代码操作
make_shared< T >(args)返回一个shared_ptr,指向一个动态分配的类型为T的对象,并用arg初始化此对象
shared_ptr< T >p (q)p是shared_ptr q的拷贝,此操作会递增q中的计数器
p = q两者都是shared_ptr,所保存的指针必须能相互转换。此操作会递减p的引用计数,递增q的引用计数
p.unique()若p.use_count()为1,则ture
p.use_count()返回与p共享的智能指针数量,可能很耗时

make_shared函数

  • 函数在动态内存中分配一个对象,并初始化它,返回指向此对象的shared_ptr
//指向一个值为42的int的shared_ptr
shared_ptr<int> p1 = make_shared<int>(42);
//指向“9999999999”的string
shared_ptr<string> p2 = make_shared<string>(10, '9');
//指向一个值初始化的int,即值为0
shared_ptr<int> p3 = make_shared<int>();
----------------------------------------------------------------
//通常,我们使用auto来保存make_shared的结果
auto p4 = make_shared<vector<string>>();

shared_ptr的拷贝和赋值(计数器)

  • 每一个shared_ptr都有一个关联的计数器,通常称其为引用计数
  • 无论我们何时拷贝一个shared_ptr,计数器都会递增:比如当用一个shared_ptr初始化拎一个shared_ptr时,或者将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数器都会加1
  • 当给shared_ptr赋予一个新值,或是shared_ptr被销毁时,计数器就会递减
  • 一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象
  • shared_ptr自动销毁所管理的对象
  • 通过析构函数完成该操作

直接管理内存

  • new就是动态分配内存的!!!
  • 在自由空间分配的内存是无名的,返回的是一个指向该对象的指针。
int *pi1 = new int;//默认初始化,值未定义
int *pi2 = new int();//值初始化,为0
//指向的对象的值为1024
int *pi = new int(1024);
//指向十个9的字符串
string *ps = new string (10,'9');

  • auto
auto p1 = new auto(obj);   
//p指向个与obj类型相同的对象,该对象用obj进行初始化
auto p2 = nwe auto{a,b,c};
//错误,括号中只能有单个的初始化器。即只能有一个参数

动态分配const对象

  • 用new分配const对象是合法的:
分配并初始化一个const int
const int *pci = new const int(1024);

内存耗尽

  • 如果内存分配失败,new抛出std::bad_alloc
  • int *p2 = new int;

释放内存delete

  • shared_ptr智能指针可以自动销毁释放内存
  • 但是通过new创建的动态内存,直到使用delete销毁前,它都是存在的
  • 不能重复对指针进行delete
  • 如果调用的函数返回值是一个动态内存分配的指针
type* func(type arg)
{
		return new type(arg);
}

void use_func(type arg)
{
		type* p = func(arg);
		//delete p;
}
//这时,需要调用者来销毁动态内存
//func内不能销毁,不然就无法传参了

  • 向shared_ptr传递删除器
    在这里插入图片描述
    在这里插入图片描述

  • 内存泄露
Foo* factory(T arg)
{
	return new Foo (arg);//函数factory使用new创建了动态内存
}

void use_factory(T arg)
{
	Foo *p = factory(arg);//调用了函数factory
	//此时没有进行delete
}
//那么当跳出该函数之后,我们就失去了指向该内存块的指针
//我们也就再也无法释放该内存
//长此以往,就会不断使得我们的可用内存减少

  • 智能指针也可能导致内存泄露
  • 如果申请了一个vector<shared_ptr > vec;
  • 如果因为一些原因,对vec进行了重排序,从而不需要了一些vec中的元素
  • 那么这样,不需要的那些元素就会被遗忘
  • 其相对应的内存就会泄漏

坚持使用智能指针,就能避免这三点

new 和 delete易出错的三点
1忘记delete内存,导致内存泄露
2使用了已经释放掉的对象
3同一块内存释放两次

shared_ptr 和 new 结合使用

  • shared_ptr<int> p(new int(42));//p是一个智能指针,指向一个int 42
  • 记住上面那种初始化就行了
  • 虽然new 和智能指针可以混合变换使用
  • 但是既然能够使用智能指针,那为什么还要用new呢?
//不能将内置指针转换为智能指针
`shared_ptr<int> p(new int(42));
`shared_ptr<int> p = new int(42); //错误,必须直接初始化
  • 不要混合使用普通指针和智能指针

  • get() 返回的指针,我们只用于向一个函数传递内置指针这一种情况
  • 并且,delete这个指针是不能删除指针对应的动态内存的;
  • 更不要用get()的值,去给一个智能指针赋值
    - **智能指针和异常**

智能指针与异常

在这里插入图片描述

  • unique_ptr
  • 由于unique_ptr是独占的,因此不支持普通的拷贝和赋值操作
unique_ptr<string> p1(new string("Stegosaurus"));
unique_ptr<string> p2(p1);    //错误: unique_ptr不支持拷贝
unique_ptr<string> p3;
p3 = p1; //错误: unique_ptr不支持赋值
unique_ptr操作
unique_ptr< type > u1空的unique_ptr,指向类型为type的对象。u1会调用delete
unique_ptr< type, D> u2来释放它的指针;u2会使用一个类型为D的可调用对象来释放它的指针
unique_ptr< type, D> u(d)空的unique_ptr,指向类型type的对象,用类型为D的对象d代替delete
u = nullptr释放u所指向的对象,将u置为空
u.release()u释放对指针的控制权,返回指针,并将u置为空
u.reset()释放u指向的对象
u.reset(q)如果提供了内置指针q,令u指向这个对象,否则将u置为空。如果u原先指向有对象,则释放。
  • 虽然我们不能拷贝或赋值unique_ptr,但是可以通过调用release或reset将指针的所有权从一个(非const)unique_ptr转移给另一个unique:
unique_ptr<string> p1(new string("Stegosaurus"));
unique_ptr< string> p2(p1.release());   //所有权从p1转移给p2,release将p1置为空
unique_ptr< string> p3(new string("Trex"));
p2.reset(p3.release());  //p2现在指向的是"Trex"
  • 不能拷贝unique_ptr有一个例外
  • 我们可以拷贝或赋值一个将要被销毁的unique_ptr
  • 最常见的例子就是函数返回以个unique_ptr;
unique_ptr< int> clone(int p)
{
	return unique_ptr<int> (new int(p));
}

  • 向unique_ptr传递删除器
  • 与shared_ptr有些许的不同,这里有两个参数
    在这里插入图片描述

  • weak_ptr
  • 这是一种不控制所指对象生存期的智能指针,它指向一个由shared_ptr管理的对象
  • 将一个weak_ptr绑定到shared_ptr并不会改变shared_ptr的引用次数
  • 一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放
  • 即使有weak_ptr指向对象,对象还是会被释放
  • 因此weak_ptr的名字就是弱!!!
我们创建一个weak_ptr时,要用shared_ptr来初始化它
auto p = make_shared<int> (42);
weak_ptr<int> wp(p);  //弱共享,p的计数并未改变

在这里插入图片描述


动态数组

  • new 和数组
int *pia = new int[get_size()]; //pia指向第一个int.//方括号中大小必须是整形,但不必是常量
//也可以这样
typedef int arrt[42];  //arrt表示42个int的整数类型
int *p = new arrt;

  • 动态数组并不是数组,我们并没有得到一个数组类型的对象,只是得到一个数组元素类型的指针
  • 也不能使用范围for语句来访问所谓的动态数组中的元素

  • 初始化
  • int *pia = new int[10]; //10个未初始化的int
  • int *pia2 = new int [10] (); //值初始化为0的int
  • string *psa = new string[10]; //10个空string
  • string *psa = new string [10] (); //10个空string

  • 还可以直接初始化
  • int *pia3 = new int[10] {0,1,2,3,4,5,6,7,8,9};
  • 如果列出的值个数少于初始化器所需的数目,则不足的部分将进行值初始化
  • 如果超出了所需的个数,则抛出bad_array_new_length的异常

  • 释放动态数组
  • delete p; //p必须指向一个动态分配对象或空
  • delete [ ] pa; //pa必须指向一个动态分配数组或空

  • allocator 类
  • new和allocator的区别:
  • new将内存分配和对象构造组合在了一起
  • 类似的,delete将对象析构和内存释放组合在了一起
  • allocator,先分配内存,在需要时在进行真正对象的创建操作

allocator<string> alloc;  //可以分配string的allocator对象
suto const p = alloc.allocate(n);  //分配n个未初始化的string

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值