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

本文深入探讨C++中四种基础内存管理工具的使用,包括new、::operatornew()、malloc的关系,及delete、free的区别。讲解了arraynew、arraydelete的底层实现,并介绍了placementnew的特殊用途。同时,文章详细解析了如何重载全局和类方法的operatornew/delete,以及设计类内存管理分配器的四步法,最后提到了newhandler在内存管理中的作用。

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

 

 

目录

一、四种基础工具的使用

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<int> ().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

  1. 提升速度,降低 malloc 的调用次数(其实影响不大):在多次使用 new 来分配内存时,每次都会调用 malloc,造成一些负担。所以考虑,能否先申请一大块内存,将其分成一个个小块备用,避免每次都调用 malloc 的问题。
  2. 节省空间,降低 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 函数,可以登记起来以备之后再次使用。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值