C/C++动态内存管理

首先我们看看 c 语言是如何进行动态内存分配的;

c 语言主要是使用malloc / calloc / realloc 来进行内存申请的。

malloc / realloc / calloc三者的共同点与不同点:

共同点
1、都是从堆上进行动态内存分配
2、释放内存都是需要使用free函数来释放
3、三者的返回值都是void*
4、都需要强制类型转换
5、都需要对申请出的空间判空(因为申请内存失败会返回空)

不同点:
1)void *malloc( size_t size );
malloc的参数是用户所需内存空间大小的字节数,不会对申请成功的内存初始化。

malloc 申请空间时并不是需要多少就申请多少,而是会多申请一些空间,1)多申请一个32字节的结构体,里面对申请的空间进行描述,2)在申请的空间前后会各多申请 4 个字节的空间,这就是保护机制,当你操作不当越界了,这 8 个字节的内容会改变,操作系统会检查前后 4 个字节是否改变了,以此判断是否越界了。
C/C++动态内存管理_第1张图片
C/C++动态内存管理_第2张图片

2)void *calloc( size_t num, size_t size );
calloc的参数:第一个:元素的个数,第二个:单个元素所占字节;会把申请成功的空间初始化为 0

3)void *realloc( void *ptr, size_t size );
realloc的参数:第一个:地址,第二个:字节数

  对于 realloc 的第一个参数:
    如果指向空,那么此时realloc 就和 malloc 是一样的;
    如果不为空,那么就将即将要申请的空间与原空间进行比较。

        如果要申请的空间比原空间小,就将原空间缩小,并返回原空间首地址
        如果要申请的空间比原空间大,那么分两种情况,第一种:新旧空间之差小于原空间大小,直接在原空间进行延伸,并返回原空间的首地址。第二种:新旧空间之差大于原空间的大小,那么直接开辟新空间,并将原空间的数据拷贝到新空间,并返回新空间首地址。C/C++动态内存管理_第3张图片



看完了 C 动态内存管理,那么来看看C++的动态内存管理:

首先 C 语言中的动态内存管理方式在 C++ 中仍然可以使用。c++ 中多了 new 这个操作符。
new申请的空间:1、不用强制类型转换;2、不用对申请出来的空间进行判空;3、可以申请时初始化这段空间。

  malloc / new / new[] 和 free / delete / delete[]
对于内置类型:如果没有配合使用,可能不会出现什么问题。
对于自定义类型:
  malloc:只是将空间动态开辟成功,并不会调用构造函数初始化空间。
  free:只是将申请的空间进行释放,并不会调用析构函数清理对象中的资源

  new:先将对象的空间开辟成功,然后调用构造函数完成对象的初始化。
  delete:先调用析构函数将对象中的资源清理,然后释放对象占用的空间
如果对一个内部有资源的自定义类型使用 malloc 开辟内存,此时调用 delete 进行空间的释放,程序就会崩溃。因为 malloc 只负责开辟空间,并不会调用对象的构造函数对其成员变量进行初始化,那么内部的成员变量是没有分配空间的,当我们调用 delete 时,delete会先对对象进行资源清理,但是对象里的资源 malloc 并没有给其分配,所以我们清理的时候是非法的操作。导致程序崩溃。

对于内部有资源的自定义类型,使用 new 开辟的空间使用 free 释放,会造成内存泄漏,因为 free 并不会调用析构函数清理对象的资源,因此会造成资源泄漏。

new底层的流程:
第一步:调用operator new() 来申请空间
第二步:调用该类的构造函数

operator new() 的工作流程:
比如 new T:底层调用 void* operator new(sizeof(T)),申请 T 类型大小的堆空间。
  在这个函数中是循环调用 malloc :1、申请空间成功:返回空间的首地址。
                 2、申请空间失败:检测用户是否提供空间不足的应对措施?如果提供应对措施,则执行应对措施,否则直接抛出 bad_alloc 类型的异常。
C/C++动态内存管理_第4张图片
new[]的流程:
第一步:调用void* operator new[](count = sizeof(T) * N + 4),如果T类的析构函数显式提供就多申请4个字节。(多申请的四个字节就是用来保存对象的个数,可以知道未来需要调用几次析构函数。)
  这里operator new 的运行流程和上图一致。
第二步:将空间前四个字节填充对象的个数,然后调用构造函数构造 N 个 T 类型对象。

delete的流程:
第一步:调用析构函数清理对象中的资源。
第二步:调用void operator delete(void* p)释放空间,void operator delete(void* p)中调用的是 free 释放空间。
C/C++动态内存管理_第5张图片
delete[] 的流程:
第一步:从第一个对象空间之前的4个字节中取对象的个数N
第二步:调用N次析构函数倒着释放(栈的特性)
第三步:void operator delete[](void* p)----这个p就是真正使用位置倒退4个字节的位置,也就是申请的空间的首地址。
在这里operator delete[](void* p) 调用 void operator delete(void* p) 调用 free()

综上:operator new 和 operator delete 实际上是由 malloc 和 free 来实现的,是对malloc 和 free 的封装。


malloc/free 和 new/delete 区别:

共同点:都在堆上申请空间,都需要手动申请 / 释放空间。
不同点:
  1)malloc/free 是函数,new/delete是标识符
  2)malloc 不会对对象进行初始化,new 可以初始化
  3)malloc 申请空间时,需要手动计算需要申请空间的大小,而new只需在后面跟上类型,编译器会自动计算大小。
  4)malloc 返回值是 void*,使用时必须要强制类型转换,而 new 并不需要强制类型转换,因为new后跟的就是类型。
  5)malloc 申请空间失败返回 NULL,因此使用时必须判空,new不需要判空,但是需要捕获异常
  6)申请自定义类型对象时,malloc/free只会开辟空间,并不调用构造/析构函数,而 new 是先申请空间,然后调用构造函数完成对象的初始化,delete 在释放空间前会先清理对象占用的资源
  7)malloc/free 的效率会比 new/delete 的高,因为 new/delete 中封装的是malloc/free。

你可能感兴趣的:(c/c++,动态内存管理,malloc具体实现,c++)