动态内存管理:malloc申请的堆块 new[]和delete[]的底层原理 malloc/free和new/delete的区别

一.new/delete new[]/delete[]的过程 

new和delete是用户在进行动态内存申请和释放的操作符,operator new 和 operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间.

1.1 内置类型

如果申请的是内置类型的空间,new 的 malloc,delete 和 free 基本类似,不同的地方是:new/delete申请的释放的是单个元素的空间,new[]的delete[] 申请和释放的是连续的空间,而且new在申请空间失败时会抛出异常,malloc会返回NULL.

operator new(封装malloc函数+异常):

  1. 内部通过malloc申请空间
  2. 如果malloc申请空间失败:
    1. 设置空间不足的应对措施,则执行,执行之后继续调用malloc再次申请空间,直到空间申请成功
    2. 如果没有设置空间不足的应对措施,直接抛异常(没有内存的异常)

operator delete:封装free函数,不会抛异常

1.2 自定义类型

①.new的原理

  1. 调用operator new 函数申请空间
  2. 在申请的空间上执行构造函数,完成对象的构造

②.delete的原理

  1. 在空间上执行析构函数,完成对象中资源的清理工作
  2. 调用operator delete 函数释放对象的空间

③.new T[N] 的原理

  1. 调用operator new[]函数,在operator new函数完成N个对象空间的申请
  2. 在申请的空间上执行N次构造函数

④.delete[]的原理

  1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
  2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

二.malloc申请的内存块

malloc申请空间时,是通过ptmalloc分配一块合适大小的内存块.申请的时候实际占有的内存要比申请的大,因为超出的空间使用来记录这块内存的管理信息. 大多数实现所分配的存储空间比所要求的要稍大一些,额外的空间用来记录管理信息——分配块的长度,指向下一个分配块的指针等等

 由上图可知,分配的内存块包括头部信息,有效载荷,填充(这里忽略).

  • 头部信息:用来描述整个块的大小以及是否已分配
  • 有效载荷:malloc用户申请的实际大小

更形象的表示:

malloc()申请的空间实际我觉得就是分了两个不同性质的空间。一个就是用来记录管理信息的空间,另外一个就是可用空间了。而用来记录管理信息的实际上是一个结构体。在C语言中,用结构体来记录同一个对象的不同信息是天经地义的事。下面看看这个结构体的原型:

//内存管理信息结构体定义:
struct mem_control_block {
        int is_available;//标记
        int size;        //实际空间的大小
    };

free()就是根据这个结构体的信息来释放malloc()申请的空间。而结构体的两个成员的大小我想应该是操作系统的事了。但是这里有一个问题,malloc()申请空间后返回一个指针应该是指向第二种空间,也就是可用空间。不然,如果指向管理信息空间的话,写入的内容和结构体的类型有可能不一致,或者会把管理信息屏蔽掉,那就没法释放内存空间了,所以会发生错误。
下面看看free()的源代码,我自己分析了一下,觉得比起malloc()的源代码倒是容易简单很多。

void free(void *ptr){
        struct mem_control_block *free;
        free = ptr - sizeof(struct mem_control_block);
        free->is_available = 1;
        return;
}

看一下函数第二句,这句非常重要和关键。其实这句就是把指向可用空间的指针倒回去,让它指向管理信息的那块空间,因为这里是在值上减去了一个结构体的大小。

注意:释放动态分配的内存时是不可以部分释放的。而且从free()的源代码看,ptr只能指向可用空间的首地址,不然,减去结构体大小之后一定不是指向管理信息空间的首地址。所以,要确保指针指向可用空间的首地址。

问题:free函数释放动态开辟的内存时,只需要将内存的首地址传过去.显然仅仅依靠首地址是无法确定释放多少内存的.由此提出问题: free是如何知道释放多少内存呢?

系统在分配内存时除了分配指定的内存空间外,还有分配用于保存内存空间大小等信息.所以内存释放时不再需要指定释放多大的内存空间,只需要指定该内存空间的首地址即可.
 

三.new[]和delete[]的底层实现(自定义类型)

new[]:申请多个自定义类型空间时,会多申请4个字节用来保存对象的个数,进而知道执行几次构造函数.

delete[]: 释放多个自定义类型的空间时,delete[]会把new[]所返回的指针向前偏移4个字节位置的地址给free.free函数接着将收到的地址向前偏移4个字节就能正确释放整个堆块.

小结:

  1. new[]在给自定义类型空间时,会多分配4个字节用来存储元素个数.通过*(int*)(ptr-1)就能看到或者直接查看内存也可以看到.所以delete用此时new[]返回的指针来调用自己底层的free时,free就出错了,程序就会崩溃.
  2. new[]在给内置类型申请空间时,不会存储元素个数,因为自定义类型delete[]时没有析构函数可调用(所以没必要存储),free不会出错

四.malloc/free 和 new/delete的区别

(1).malloc/free和new/delete的共同点:

  1. 都是从堆上申请空间,并且需要用户手动释放.

(2).不同的地方是:

  1. malloc和free是函数,new和delete是操作符
  2. malloc申请的空间不会初始化,new可以初始化
  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
  4. malloc的返回值为void*,在使用时必须强转,new不需要,因为new后跟的空间类型
  5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕捉异常
  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数和析构函数,而new在申请空间后会自动调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值