IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++内存管理 -> 正文阅读

[C++知识库]C++内存管理

四大基本构件

在这里插入图片描述

一、new/delete

1.new/delete的调用过程
new的调用过程是:编译器会调用到咱们自己重载的new全局函数,然后调用对象的构造函数。
delete的调用过程:首先是调用对象的析构函数,然后调用重载的delete函数,释放空间
底层还是使用malloc和delete
在这里插入图片描述
在这里插入图片描述
注意:这就是new/delete与malloc/free的区别,虽然new/delete底层还是调用malloc和free,但是new/delete多出来的操作是调用了对象的构造函数和析构函数。对于内置对象像int、char类型没什么区别,但是对于用户自定义的对象来说,只能使用new和delete。

2.对象不能直接调用类的析构函数和构造函数

二、new[]/delete[]

1.调用过程
new[]分为两种情况:

  • 简单数据类型( 包括基本数据类型和不需要析构函数的类型)。new[] 调用的是operator new[],计算出数组总大小之后调用operator new,分配出总的空间,和直接调用malloc没有区别。值得一提的是,可以通过()初始化数组为零值。
    在这里插入图片描述

  • 复杂数据类型, new[]先调用operator new[]分配内存,然后在p的前四个字节写入数组大小,最后调用三次构造函数。
    实际分配的内存块如下:
    在这里插入图片描述
    复杂数据类型必须要记录数组的长度。因为释放内存之前会调用每个对象的析构函数。但是编译器并不知道p实际所指对象的大小。如果没有储存数组大小,编译器如何知道该把p所指的内存分为几次来调用析构函数呢?
    对于一个自定义类型。它空间的头部会有一个分配了多少个空间,也就是[]里面的数字。new[]和delete[]两个调用p时,p指向的空间不同。并且只调用delete p会报错
    在这里插入图片描述
    delete[]的调用过程:对象已经分配了多少空间就释放多少空间。

注意:
在这里插入图片描述
2.内存泄露的情况
注意:如果分配空间的时候是调用new[],释放空间的时候调用的是delete,这时候会不会产生内存泄露要分为两种情况。

  1. 分配的对象里面没有指针,比如上面的Complex(复数)类,里面没有指针,那么使用delete和delete[]的效果是一样的,因为尽管只释放了一个空间,但是其它的空间都会被操作系统回收,不会发生内存泄露。
  2. 分配的对象里面包含指针,那么使用delete会发生内存泄露。因为delete只释放了分配的内存,而分配的内存里面指向的指针没有被全部释放掉,只释放了一个。所以必须调用delete[],它会为每一个分配的对象调用析构函数,将指针指向的空间也释放掉。

3.分配对象和释放对象的顺序
在调用new[]时,首先是调用new[0]、new[1]…
在调用delete[]时,首先调用delete[n]、delete[n-1]…delete[0].
在这里插入图片描述

三、placement new

定点放置new,在一个指定的空间上分配内存。
格式是:int *p=new(buf) int;底层是调用重载的new函数,直接返回buf的地址,没有调用malloc。
注意:placement new 没有对应的placement delete,因为它没有调用malloc分配空间
在这里插入图片描述

四、重载operator new/operator delete

1.调用过程:
operator new/operator delete底层仍然是调用malloc/free。
在这里插入图片描述

2.重载
operator new/operator delete是可以被重载的。一般是重载类成员函数,全局函数重载比较少见
在这里插入图片描述

  • 重载全局的operator new/operator delete
    在这里插入图片描述

  • 重载局部的operator new/operator delete
    在这里插入图片描述
    注意:这个时候重载的operator new/operator delete必须是静态成员函数

  • 重载局部的operator new[]/operator delete[]
    在这里插入图片描述

  • placement new的重载

    重载的示例:
    在这里插入图片描述

  • placement delete的重载
    正常情况下不会调用placement delete,只有在构造函数抛出异常时,此时对象未构造完成,无法调用析构函数,但是已经分配了空间,此时使用placement delete释放掉已经分配的空间
    在这里插入图片描述

内存管理

频繁调用malloc可能会很慢,每一次调用malloc都会在分配的空间的头部加上cookie,内存管理就是为了解决这两个问题。减少调用malloc,使得更快。减少cookie使得消耗的空间变小。

一、设计一

在这里插入图片描述
1、这种方法设计的思想是:
如果一个对象可能频繁的需要调用new去分配空间,那么频繁的调用malloc会很慢(实际上malloc并不慢,其实这个因素不是关键的)。还有一个问题是,如果频繁的调用new,那么每一个分配的空间都带有cookie,这样会浪费大量空间。
那么改进的方法就是:可以重载operator new,使得调用这个函数时会给一个比较大的空间。然后只需要调用一次new来分配空间,减少了malloc的调用,增加了速度。并且只有一个cookie,节省了空间。这种设计的实现用到了一个指针,这个指针指向分配空间的首地址,并且将这个空间设计为一个链表。那么每一次调用new时,只有第一次才需要调用new分配一个大的空间,然后调用new时,只需要判断这个空间是否为空就行。不为空,就将p指向的空间分配给调用new的对象,然后让p=p->next;

2.存在的缺陷:
增加了一个next指针,类的大小增加了四个字节。如果类本身就比较小的话,就比如四个字节,那么就相当于将类的大小扩大了一倍。这就是存在的缺陷。

3.实例
这个例子有100个指针,其实前24个指针,只调用了一次new,也只有一个cookie,间隔是8就可以看出来。间隔16表示cookie原来就带了8个字节,在加上对象的8个字节就是16个字节。24就是上面那一张图默认分配的24个对象的空间。
在这里插入图片描述

二、设计二

与设计一相比,设计二就是设计了一个union类,共享对象和next指针的内存,这样就节省了空间。一个空间被分配之后它的next将没用,所以空间分配好后,在调用set初始化类的数据,将next指针的空间覆盖掉。
在这里插入图片描述
示例
在这里插入图片描述

三、设计三

因为操作都比较类似,每一个类就需要重新设计一个operator new,比较繁琐。所以设置一个能重用的类alloctaor。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、设计四

将上面的操作定义为宏
在这里插入图片描述
在这里插入图片描述

alloc运行模式

容器的alloc。

一、结构

1.它维护了一个叫free_list[16]的数组,这个数组的每一个元素都指向一个链表。下标为0的元素代表大小为8个字节,1为16个字节,2为24个字节,依次类推,最后一个下标15为128个字节。如果对象的大小不是8的倍数,那么会将它向上调整到最接近的8的倍数。

就比如如果对象大小是6个字节,那么会将它调整到8个字节。虽然这样看起来每一个元素浪费了两个字节,但是这个链表里面的元素是不带cookie的,cookie是8个字节,所以这样设计的空间肯定是节省了的。

对于大小大于128的对象,不会使用alloc。它会直接调用malloc。
在这里插入图片描述

使用union的结构,假设这个被分配的空间的地址是p。那么当它被分配完成后,空闲空间就等于p->next。然后这个next将会被要填入的元素覆盖掉。
在这里插入图片描述

二、演示

1.初始状态,没有元素。
在这里插入图片描述

2.假如现在有一个对象,对象大小为32个字节,申请一个空间。申请空间还是会调用malloc,不过他会申请一个比较大的空间,不会仅申请32个字节。那么这个空间只会携带一个cookie。

这个空间是多大呢?
这个空间的大小是32202+RoundUp(0>>4)=1280。实际上是申请了40个对象的大小,在加上后面的附加量。附加量是已经申请的总内存除以16,附加量会越来越大,这也是源于生活经验,计算机申请的空间会越来越大,所以附加量越来越大。申请的40个对象大小,第1个返回给用户,接下来19个连在链表上。下一次又需要分配内存,就不需要调用malloc了,直接用连接在链表上的空间。剩余的20个空间留作备用。此时备用空间pool的大小是640.
在这里插入图片描述
3.又有一个对象是64个字节。那么不会直接调用malloc,因为此时pool空间的大小是640个字节,直接使用pool空间。此时64个字节下的链表有640/10=10个元素。第一个元素被分配给了用户,剩余9个空闲的空间。此时pool空间的大小为640%10=0;
在这里插入图片描述
4.又有一个对象,申请的空间是96个字节。pool无余量,所以又需要调用malloc来分配空间。此时pool大小是2000.
在这里插入图片描述
5.一个对象又申请88个字节的空间。pool余量充足,给88个字节的链表分配了20个空间之后,还剩余240.
在这里插入图片描述
6.连续申请3次88,直接从list#10也就是88字节的链表下取出空间返回给用户。
在这里插入图片描述
7.看图
在这里插入图片描述
8。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、源码

源码就是上面思想的具体实现
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、示例

1.下面两种插入方式都没有cookie,我们知道,push_back()底层还是调用placement new,但是因为采用的分配器是alloc,已经有分配好的空间了。
所以每一次的placement new只需要向链表请求空间就行,不需要调用malloc
在这里插入图片描述

2.普通的list容器使用的分配器每个元素都带cookie。在默认的情况下,分配器不是使用alloc,那么每一次插入元素都需要调用malloc,所以都会带cookie。
而对于list这个容器来说,它的空间是每插入一个元素就分配一次,那么如果是使用一级分配器的话,每次就要调用malloc,所以它的每一个元素都有cookie。这样二级分配器会好得多。不过默认情况下还是使用一级分配器。

在这里插入图片描述

3.关于容器的内存分配------以vector为例
对于一个vector来说,它的内存分配是通过allocator来实现的。
一般而言,我们习惯的C++内存配置操作和释放操作是这样的:
Foo* pf=new Foo; //配置内存,然后构造对象
delete pf; //将对象析构,然后释放内存
这里的new包含两段操作,先调用::operator new分配内存,然后调用Foo::Foo()构造对象。
delete也包含两个操作,先调用析构函数,然后调用::operator delete释放内存。
为了精密分工,STL allocator决定将这两个操作分开。内存分配由alloc::allocate()负责,内存释放由alloc::deallocate()负责;对象构造操作由::construct()负责,对象析构由::destroy()负责。
在这里插入图片描述
(1)构造和析构函数
构造函数调用placement new,调用构造函数,placement new没有调用malloc分配空间,是将对象放到了一个已经分配好的空间。
析构函数直接调用
在这里插入图片描述
(2)内存空间的分配与释放
考虑到小区块可能造成的内存破碎问题,SGI设计了双层配置器。第一层配置器直接使用malloc和free。第二层配置器使用alloc。
在这里插入图片描述
在这里插入图片描述

对于vector容器来说,它有着扩容机制,当空间不够时,它会调用allocate来分配双倍的空间。
iterator new_start = data_allocator::allocate(len).
在这里插入图片描述
一级分配器
在这里插入图片描述
它会使用上面这条语句分配长度为len个对象的空间,一般为原来空间的两倍。默认情况下,vector是直接调用malloc和free的,也就是一级分配器。因为它是一次性分配空间,不需要频繁调用malloc,所以直接使用一级分配器即可。每一个元素插入时,不会调用push_back(),所以每一个元素都不带cookie。

五、缺陷

1.它没有调用free和delete,尽管内存需要释放,但是只会将空闲的空间继续加到链表中。不会还给操作系统。在多进程的任务中,可能会让其它进程无内存可用。
在这里插入图片描述

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-15 11:29:21  更:2022-05-15 11:30:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 18:57:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码