动态内存

Posted summer-8918

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态内存相关的知识,希望对你有一定的参考价值。

到现在为止,我们编写的程序中所使用的对象都有着严格定义的生存期。

全局对象在程序启动时分配,在程序结束时销毁;

对于局部自动对象,当我们进入其定义所在的程序块时被创建,在离开块时销毁;

局部static对象在第一次使用前分配,在程序结束时销毁。

除了自动和static对象外,C++还支持动态分配对象。动态分配的对象的生存期与它们在哪里创建无关,只有当显式地被释放时,这些对象才会销毁。

 

动态对象的正确解释呗证明是编程中及其容易出错的地方。为了更安全地使用对象,标准库定义了两个只能指针类型来管理动态分配的对象。当一个对象应该被释放时,指向它的智能指针可以确保自动地释放它。

 

我们的程序到目前为止只使用过静态内存或栈内存。静态内存用来保存局部static变量,类static数据成员,以及定义在任何函数之外的变量。

栈内存用来保存定义在函数内的非static对象。分配在静态或栈内存中的对象由编译器自动创建和销毁。对于栈对象,仅在其定义的程序块运行时才存在;static对象在使用之前分配,在程序结束时销毁。

 

除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存被称作自由空间或堆。程序用堆来存储动态分配的对象——即,那些在程序运行时分配的对象。

动态对象的生存期由程序性来控制,也就是说,当动态对象不再使用时,我们的代码必须显式地销毁它们。

 

在C++中,动态内存的通过一对运算符来完成:new和delete

new:在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化

delete:接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

 

动态内存的使用很容易出现问题,因为确保在正确的时间释放内存是极其困难的。

(1)  忘记释放内存,会产生内存泄露

(2)  尚有指针引用内存的情况下释放它,会产生引用非法内存的指针

新的标准库提供了两种智能指针类型来管理动态对象:

shared_ptr允许对个指针指向同一个对象;

unique_ptr则“独占”所指向的对象

weak_ptr是一种弱引用,指向shared_ptr所管理的对象

这三种类型都定义在memory头文件中

 

1 shared_ptr类

shared_ptr<string> p1    //指向string

默认初始化的智能指针中保存着一个空指针

shared_ptr和unique_ptr都支持的操作

shared_ptr<T> sp

空智能指针,可以指向类型为T的对象

unique_ptr<T> up

p

将p用作一个条件判断,若p指向一个对象,则为true

*p

解引用p,获得它指向的对象

p->men

等价于(*p).men

p.get()

返回p中保存的指针。要小心使用,若智能指针释放了其对象,返回的指针所指向的对象就消失了

swap(p,q)

交换p和q中的指针

p.swap(q)

 

shared_ptr独有的操作

make_shared<T> (args)

返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化此对象

shared_ptr<T> p(q)

p是shared_ptr q的拷贝;此操作会递增q中的计数器。q中的指针必须能转换为T*

p=q

p和q都是share_ptr,所保存的指针必须能相互转换。此操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则将其管理的原内存释放掉

p.unique()

若p.use_count()为1,返回true;否则返回false

p.use_count()

返回与p共享对象的智能指针数量;可能会很慢,主要用于调试

 

最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。

shared_ptr<int>p3 = make_shared<int>(42);

//指向一个值为42的int的shared_ptr

 

可以认为每个shared_ptr都有一个关联的计数器,通常称为引用计数器。无论何时考诶一个shared_ptr,计数器都会递增。当我们给shared_ptr赋予一个新值或是shared_ptr被销毁,计数器就能递减。

一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。

 

shared_ptr自动销毁所管理的对象——通过构析函数完成销毁工作。

如果你将shared_ptr存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用erase删除不再需要的那些元素。

 

使用了动态生存期的资源的类

程序使用动态内存出于以下三种原因之一:

1 程序不知道自己需要使用多少对象

2 程序不知道所需对象的准确类型

3 程序需要在多个对象间共享数据

容器类是出于第一种原因使用动态内存的典型例子。

使用动态内存的一个常见原因是允许多个对象共享相同的状态。

 

2 直接内存管理

使用new动态分配和初始化对象

int *pi = new int;

//pi指向一个动态分配的、未初始化的无名对象

在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针。

在新标准下,可以使用列表初始化

int *pi=new int(1024)

//pi指向的对象的值为1024

int *pi1=new int;    //默认初始化,*pi1的值未定义

int *pi2=new int();      //默认初始化,pi2为0

 

处于与变量初始化相同的原因,对动态分配的对象进行初始化通常是个好主意;

类似其他任何const对象,一个动态分配的const对象必须进行初始化。

 

释放动态内存

delete

delete p;     //p必须指向一个动态分配的对象或是一个空指针

由内置指针(而不是智能指针)管理的动态内存在被显示释放前一直都会存在。

在delete之后,指针就编程了空悬指针,即,指向一块曾经保存数据对象但现在已经无效的内存的指针。

使用new和delete管理动态内存存在三个常见问题:

  1. 忘记delete内存
  2. 使用已经释放掉的对象
  3. 同一块内存释放两次

 

用new返回的指针来初始化智能指针

shared_ptr<int> p1(new int(1023))  //使用直接初始化

 

使用一个内置指针来访问一个智能指针所负责的对象是很危险的,因为我们无法知道对象何时会被销毁。

get用来将指针的访问权限传递给代码,你只有在确定代码不会delete指针的情况下才能使用get。特别是,永远不要用get初始化另一个智能指针或者为另一个智能指针赋值

 

为了正确使用智能指针,我们必须坚持一些基本规范:

1 不适应相同的内置指针初始化(或reset)多个智能指针;

2 不delete get()返回的指针

3 不使用get()初始化或reset另一个智能指针

4 如果你使用get() 返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了

5 如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器

 

3 unique_ptr

某个时刻只能有一个unique_ptr指向一个给定对象,当unique_ptr被销毁时,它所指向的对象也被销毁

unique_ptr<int> p2(new int(42))

//p2指向一个值为42的int

由于一个unique_ptr拥有它指向的对象,因此它不支持普通的拷贝或赋值操作

unique_ptr操作

unique_ptr<T> u1

空unique_ptr,可以指向类型为T的对象。u1会使用delete来释放它的指针;u2会使用一个类型为D的可调用对象来是否它的指针

unique_ptr<T,D> u2

unique_ptr<T,D> u(d)

空unique_ptr,指向类型为T的对象,用类型为D的对象d代替delete

u=nullptr

是否u指向的对象,将u置为空

u.release()

u放弃对指针的控制权,返回指针,并将u置为空

u.reset()

释放u指向的对象

u.reset(q)

如果提供了内置指针q,另u指向这个对象;否则将u置为空

u.reset(nullptr)

 

调用release会切断unique_ptr和它原理管理的对象间的联系。release返回的指针通常被用来初始化另一个智能指针,或给另一个智能指针赋值

 

4 weak_ptr

weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。

weak_ptr <T> w

空weak_ptr可以指向类型为T的对象

weak_ptr<T> w(sp)

与shared_ptr sp指向相同类型的weak_ptr。T必须能转换为sp指向的类型

w=p

p可以是一个shared_ptr或一个weak_ptr。赋值后w与p共享对象

w.reset()

将w置为空

w.use_count()

与w共享对象的shared_ptr的数量

w.expired()

若w.use_count()为0,返回true,否则返回false

w.lock()

如果expired未true,返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr

 

当我们创建一个weak_ptr时,要用一个shared_ptr来初始化它:

auto p =make_shared<int> (42);

weak_ptr<int> wp(p);

//wp弱共享p;

由于对象可能不存在,不能使用weak_ptr直接访问对象,必须调用lock。

 

5 动态数组

一次性为很多元素分配内存的方法:

(1)  new,分配并初始化一个对象数组

(2)  allocator,允许将分配和初始化分离

使用容器的类可以使用默认版本的拷贝、赋值和析构操作。分配动态数组的类必须定义自己的操作,在拷贝、赋值以及销毁对象时管理所关联的内存。

new分配要求数量的对象并返回指向第一个对象的指针

int *pia = new int[get_size()];

//pia指向第一个int

分配一个数组会得到一个元素类型的指针。

动态数组不是数组类型。

int *pia = new int[10];

//10个未初始化的int

int *pia2 = new int[10]();

//10个值初始化为0的int

释放动态数组

delete p;

//p必须指向一个动态分配的对象或为空

delete [] pa;

//pa必须指向一个动态分配的数组或为空

智能指针和动态数组

unique_ptr<int[]> up(new int[10]);

up.release();     //自动用delete[] 销毁其指针

类型说明符中的方括号(<int[]>)指出up指向一个int数组而不是一个int。

指向数组的unique_ptr

指向数组的unique_ptr不支持成员访问运算符(.和->)

 

其他的unique_ptr操作不变

 

unique_ptr<T[]> u

u可以指向一个动态分配的数组,数组元素类型为T

unique_ptr<T[]> u(p)

u指向内置指针p所指向的动态分配的数组。p必须能转换为类型T*

u[i]

返回u拥有的数组中位置i处的对象u必须指向一个数组

shared_ptr未定义下标运算符

allocatoe类

将内存分配和对象构造分离;在memory库中;将内存分配和对象构造分离开来;提供一种类型感知的内存 分配方法,它分配的内存是原始的、未构造的。

allocator<string> alloc;

//可以分配string的allocator对象

auto const p = alloc.allocate(n);

//分配n个未初始化的string

标准allocator类及其算法

allocator<T> a

定义了一个名为a的allocator对象,它可以为类型为T的对象分配内存

a.allocate(n)

分配一段原始的、为构造的内存,保存n个类型为T的对象

a.deallocate(p,n)

释放从T*指针p中地址开始的内存,这块内存保存了n个类型为T的对象;p必须是一个先前由allocate返回的指针,且n必须是p创建时所要求的大小。在调用deallocate之前,用户必须对每个在这块内存中创建的对象调用destroy

a.construct(p,args)

p必须是一个类型为T*的指针,指向一块原始内存;arg被传递给类型为T的构造函数,用来在p指向的内存中构造一个对象

a.destroy(p)

p为类型T*指针,此算法对p指向的对象指向析构函数

 

为了适应allocate返回的内存,必须用construct构造对象,使用未构造的内存,其行为是未定义的。

只能对真正构造了的元素进行destroy操作

标准库还未allocator类定义了两个伴随算法,可以在未初始化内存中创建对象

uninitialized_copy(b,e,b2)

以上是关于动态内存的主要内容,如果未能解决你的问题,请参考以下文章

玩转C语言动态内存,轻松管理动态内存空间

动态内存管理

Linux 动态库如何节约内存

关于动态内存管理的一些理解

动态内存管理

动态内存分配