c++内存管理 第一讲 primitives

一、四种内存分配和释放方法

在编程时可以通过上图的几种方法直接或间接地操作内存。下面将介绍四种C++内存操作方法:

通常可以使用malloc和new来分配内存,当然也可以使用::operator new()和分配器allocator来操作内存,下面将具体介绍这些函数的使用方法。对于不同的编译器,其allocate函数的接口也有所不同:

向alloc要多少大小,就要dealloc多少大小,这个只有容器才能做到!

对于GNU C,不同版本又有所不同:

这张图中的__gnu_cxx::__pool_alloc().allocate()对应于上张图中的allocator().allocate()。

通过malloc和new分配内存、通过free和delete释放内存是十分常用的,通过::operator new操作内存比较少见,allocator分配器操作内存在STL源码中使用较多,对于不同的编译环境使用也有所不同。

二、基本构件之 new/delete expression

1、内存申请

上面这张图揭示了new操作背后编译器做的事:

  • 1、第一步通过operator new()操作分配一个目标类型的内存大小,这里是Complex的大小;
  • 2、第二步通过static_cast将得到的内存块强制转换为目标类型指针,这里是Complex*
  • 3、第三版调用目标类型的构造方法,但是需要注意的是,直接通过pc->Complex::Complex(1, 2)这样的方法调用构造函数只有编译器可以做用户这样做将产生错误

值得注意的是,operator new()操作的内部是调用了malloc()函数

_callnewh:调用new_handler

2、内存释放

同样地,delete操作第一步也是调用了对象的析构函数,然后再通过operator delete()函数释放内存本质上也是调用了free函数

三、Array new

上图主要展示的是关于new array内存分配的大致情况。当new一个数组对象时(例如 new Complex[3]),编译器将分配一块内存,这块内存首部是关于对象内存分配的一些标记,然后下面会分配三个连续的对象内存,在使用delete释放内存时需要使用delete[]。如果不使用delete[],只是使用delete只会将分配的三块内存空间释放,但不会调用对象的析构函数,如果对象内部还使用了new指向其他空间,如果指向的该空间里的对象的析构函数没有意义,那么不会造成问题,如果有意义,那么由于该部分对象析构函数不会调用,那么将会导致内存泄漏。图中new string[3]便是一个例子,虽然str[0]、str[1]、str[2]被析构了,但只是调用了str[0]的析构函数,其他对象的析构函数不被调用,这里就会出问题。

下面将演示数组对象创建与析构过程:

接下来将更具体地展示new array对象的内存分配情况:

如果使用new分配十个内存的int,内存空间如上图所示,首先内存块会有一个头和尾,黄色部分为debug信息,灰色部分才是真正使用到的内存,蓝色部分的12bytes是为了让该内存块以16字节对齐。在这个例子中delete pi和delete[] pi效果是一样的,因为int没有析构函数。但是下面的例子就不一样了:

上图通过new申请三个Demo空间大小,内存块使用了96byte,这里是这样计算得到的:黄色部分调试信息32 + 4 = 36byte;黄色部分下面的“3”用于标记实际分配给对象内存个数,这里是三个所以里面内容为3,消耗4byte;Demo内有三个int类型成员变量,一个Demo消耗内存3 * 4 = 12byte,由于有三个Demo,所以消耗了12 * 3 = 36byte空间;到目前为止消耗36 + 4 + 36 = 76byte,加上头尾cookie一共8byte一共消耗84byte,由于需要16位对齐,所以填充蓝色部分为12byte,一共消耗了84 + 12 = 96byte。这里释放内存时需要加上delete[],上面分配内存中有个标记“3”,所以编译器将释放三个Demo对象空间,如果不加就会报错。

四、placement new

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NBMuSvOB-1663989457820)(https://camo.githubusercontent.com/3392987755392c06f319b1fdcfe0b1d26ecf38a3adeeb3af5880161e7ad77392/68747470733a2f2f692e696d6775722e636f6d2f6157794275746c2e706e67)]

五、重载

1、C++内存分配的途径

如果是正常情况下,调用new之后走的是第二条路线,如果在类中重载了operator new(),那么走的是第一条路线,但最后还是要调用到系统的::operator new()函数,这在后续的例子中会体现。

对于GNU C,背后使用的allocate()函数最后也是调用了系统的::operator new()函数

2、重载new 和 delete

上面这张图演示了如何重载系统的::operator new()函数,该方法最后也是模拟了系统的做法,效果和系统的方法一样,但一般不推荐重载::operator new()函数,因为它对全局有影响,如果使用不当将造成很大的问题

如果是在类中重载operator new()方法,那么该方法有N多种形式,但必须保证函数参数列表第一个参数是size_t类型变量;对于operator delete(),第一个参数必须是void* 类型,第二个size_t是可选项,可以去掉。

对于operator new[]和operator delete[]函数的重载,和前面类似。

五、pre-class allocator

每个对象以8byte对齐。内存池本质上是分配了一大块内存,然后将该内存分割为多个小块通过链表拼接起来,所以物理上不一定连续但是逻辑上是连续的。

使用了union保存链表元素的next指针,这样整体上可以节省空间;其次是delete函数,它并没有直接将目标元素删除,而是将它当作下一个可分配的内存空间,也就是说如果delete某元素,那么该元素占有的内存空间不会被free掉,而是在下一次调用new时分配给新的对象。

六、static allocator

分配的时候,每5=chunk个是一个整体

之前的几个版本都是在类的内部重载了operator new()和operator delete()函数,这些版本都将分配内存的工作放在这些函数中,但现在的这个版本将这些分配内存的操作放在了allocator类中,这就渐渐接近了标准库的方法。从上面的代码中可以看到,两个类Foo和Goo中operator new()和operator delete()函数等很多部分代码类似,于是可以使用宏来将这些高度相似的代码提取出来,简化类的内部结构,但最后达到的结果是一样的:

七、global allocator

上面我们自己定义的分配器使用了一条链表来管理内存的,但标准库却用了多条链表来管理,这在后续会详细介绍:

八、new handler

如果用户调用new申请一块内存,如果由于系统原因或者申请内存过大导致申请失败,这时将抛出异常,在一些老的编译器中可能会直接返回0,可以参考上图右边代码,当无法分配内存时,operator new()函数内部将调用_calnewh()函数,这个函数通过左边的typedef传入,看程序员是否能自己写一个handler处理函数来处理该问题。一般有两个选择,让更多的Memory可用或者直接abort()或exit()。下面是测试的一个结果:

该部分中自定义了处理函数noMoreMemory()并通过set_new_handler来注册该处理函数,在BCB4编译器中会调用到自定义的noMoreMemory()函数,但在右边的dev c++中却没有调用,这个还要看平台。

九、=default和=delete

defualt只有拷贝构造,赋值和析构

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值