C++ 内存管理(一)primitives 基础工具

在这里插入图片描述

在这里插入图片描述

目录

一、四种基础工具的使用

1.1 四种工具的介绍

1.1.1 new、::operator new()、malloc 三者之间的关系

1.1.2 delete、free

1.1.3 array new、array delete

1.1.4 placement new

1.2 实现内存管理工具的重载

1.2.1 重载全局版本 ::operator new / ::operator delete

1.2.2 重载类方法 operator new / operator delete

1.2.3 重载大于一个参数时的 operator new() / delete()

1.3 类的内存管理(分配器设计)

第一步:per_class allocator

第二步:嵌入式指针

第三步:static allocator

第四步:宏定义实现

1.4 内存管理的小工具 new handler

1.4.1 new handler 的概念

1.4.2 new handler 的使用

一、四种基础工具的使用

在这里插入图片描述

::operator new() 与 ::operator delete() 作为C++函数,其用法与 new 和 delete 基本相同,其底层实现是调用了 malloc 和 free 。
分配器 allocator ().allocate(n) ()表示生成临时对象,其 deallocate 需要标注释放对象的大小,对用户直接使用很不友好,所以一般不直接使用 allocator。
1.1 四种工具的介绍

1.1.1 new、::operator new()、malloc 三者之间的关系

在这里插入图片描述

new 操作实际包含 分配适当大小内存、指针类型转换、调用构造函数 三个步骤:
分配内存通过 new —> 调用 operator new() —> 调用 malloc() 实现,sizeof() 指定类的分配大小。
1.1.2 delete、free

在这里插入图片描述

delete 实际操作包括 调用析构函数、释放内存 两个步骤:

析构函数是可以通过 new 指令得到的指针调用的 pc->~Complex();

释放内存操作通过 operator delete() (—>调用 free())实现。

1.1.3 array new、array delete

在这里插入图片描述

底层还是调用 malloc 和 free 。
使用 array new 会使分配的内存带上一个小小的 cookie 信息,主要存储申请的内存块大小(delete[] 时才会知道要释放到少内存)等信息。
如果使用了 array new 时,析构却忘了加上 [],本身 new 申请的连续内存部分因为 cookies 的存在会被正常释放。当所创建的类对象中有指针成员时,由于没有正常调用到所有成员的析构函数,所以指针指向的内存部分不会被正常释放,从而造成内存泄露。

在这里插入图片描述

由于不能直接使用 new 语句得到的指针去调用构造函数,但是可以使用 placement new 方法:new(tmp++) A(i) 调用构造函数设初值。

在这里插入图片描述

61h 是记录大小的 cookie, 说明本次申请的内存大小为 60h,最后一 bit 设为 1 表示状态为 on 。
pad 是 malloc 内存对齐 16bytes 所要求的。
1.1.4 placement new

在这里插入图片描述

placement new 允许我们将 object 构建于一块已经分配好的内存中。 Complex* pc = new(buf) Complex(1,2) 中 buf 是指向一块已经分配好空间的内存的指针,在所指的地方调用构造函数生成对象。
没有所谓的 placement delete,因为 placement new 根本没有分配内存。
1.2 实现内存管理工具的重载
从何处入手去实现重载?

在这里插入图片描述

由于 new 表达式 的调用轨迹是通过 new —> 调用 ::operator new() —> 调用 malloc() 实现的。由于重载全局的 ::operater new 影响太大,所以考虑通过重载类方法 Foo::operator new 来接管并更改内存分配的途径。

自己编写 Foo::operator new 方法。实现内存池分成小块管理 ,去除 cookie 冗余,最后还是会调用 malloc 实现内存分配。

在这里插入图片描述

1.2.1 重载全局版本 ::operator new / ::operator delete
在这里插入图片描述

callnewh() 方法实现当内存分配失败时,由自定义的方法回收一部分内存,再去做重新分配,直到分配成功为止。

1.2.2 重载类方法 operator new / operator delete

在这里插入图片描述

operator new 函数重载 必须是 static,因为使用 new 来创建一个对象之前对象是不存在的,所以无法通过它来调用这个函数。

在这里插入图片描述

若使用 Foo* p = ::new Foo(7); 则会选择全局版本的 new,绕过上述所有重载版本的函数。

1.2.3 重载大于一个参数时的 operator new() / delete()

在这里插入图片描述

使用默认 Foo* pf = new Foo;即 operator new 时,Foo 的大小作为默认第一参数传给了 new, 其他有第二或者多参数的重载都是对 operator new() 的重载,同样也不用写第一参数 size_t 的值。

在这里插入图片描述

在这里插入图片描述

1.3 类的内存管理(分配器设计)
第一步:per_class allocator
提升速度,降低 malloc 的调用次数(其实影响不大):在多次使用 new 来分配内存时,每次都会调用 malloc,造成一些负担。所以考虑,能否先申请一大块内存,将其分成一个个小块备用,避免每次都调用 malloc 的问题。
节省空间,降低 cookie 的用量:每次 malloc 的内存块上下都会有 cookie,而提前申请一大块内存的话,每个小块的上下都没有 cookie ,整个申请的内存池上下只存在两个 cookie 。

在这里插入图片描述

为了去除 cookie 设计了一个指向本身的指针,将申请的内存 池都串联起来。p 作为返回值被回传,指向申请内存的起始位置,freestore 指向整个链表的头结点,实际链表的元素数为23。

delete 中没有使用 free,实际上来说,内存的确没有还给操作系统,这里只是实现了内存的回收,放在链表开头,给下一次使用时备用。但是不存在所谓的内存泄漏,因为所有的内存都在链表的管理权限之内,没有无法管理的部分。

这个版本的分配器只针对类实现,用类成员 *next 来辅助管理,重载了类方法 operator new 和 delete。

在这里插入图片描述

第二步:嵌入式指针

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

引入 union 将所定义的的数据 struct 与指针 next 放入一个共用体中,即嵌入式指针。

匿名共用体没有名称,其成员将成为位于相同地址处的变量,可直接由类对象名调用。

第三步:static allocator

在这里插入图片描述

将内存管理从类中独立出来,实现一个 allocator 类,维护一条链表。allocator 作为需要实现管理的类的成员。

放弃 union 的设计,进一步简化为:类对象中声明一个只有一个指针成员的 struct ,指针赋值时指向下一个 struct(p->next = (obj*)((char*)p + size)),从而实现了每个内存块的前4个字节存放 next 指针指向下一个的效果。

新建类对象使用时:

在这里插入图片描述

测试结果:

在这里插入图片描述

第四步:宏定义实现

在这里插入图片描述

使用方法与类基本不相关,十分规程化。考虑使用宏定义。

1.4 内存管理的小工具 new handler
1.4.1 new handler 的概念
在这里插入图片描述

operator new 函数中处理分配内存不足时所做的 callnewh 操作,顾名思义就是调用 new handler。

new handler 要完成的任务:

释放一些可能无用的内存
调用 abort() 或 exit(),终止程序
1.4.2 new handler 的使用

在这里插入图片描述

set_new_handler() 函数参数为新设置的 new handler 函数,返回值为之前所使用的 handler 函数,可以登记起来以备之后再次使用。

转载自:原博文地址,点击跳转。

个人总结心得
使用virsual Studio2019默认编译器的全局operator new,并不会产生侯捷老师所说的间隔是16,而是等于自定义版本的无Cookie:8(也许现在默认的分配内存方案已经优化?)
Screen* p = ::new Screen[N]; //使用全局New去构造连续的内存区间

        Screen* p[N];
        for (int i = 0; i < N; ++i)
            p[i] = ::new Screen(i);	//使用全局placement New去调用构造函数得话,无法保证分配的内存是连续的,很大几率是离散的。 
        Screen* p[N];
        for (int i = 0; i < N; ++i)
            p[i] = new Screen(i);	//使用自定义的operator New去调用构造函数得话,可以自定义分配的内存是连续的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值