malloc的底层实现原理

首先了解内存分配:

  • 一些全局变量、static变量是在编译期间就为他们分配好内存空间的,他们都被放在静态存储区,生命周期随进程。
  • 局部变量是在程序运行期间才为他们分配空间,在栈上进行分配,一旦离开该局部作用域,栈上变量即会被销毁,栈上空间有限。
  • 程序运行时我们使用malloc/new申请的空间都是在堆上进行分配的,当手动调用free/delete时才会被销毁,它的生命周期由用户来决定。

学习C语言时我们学习了使用malloc/calloc/realloc来动态申请一块联系可用的空间,用free进行内存释放。

函数原型:
这里写图片描述
他们之间的区别:

  • malloc需要手动计算所需空间的字节数作为他的参数传递过去。
  • calloc中第一个参数是元素个数,第二个是单个元素大小。并且calloc会将空间中每个字节初始化为0
  • realloc是对动态开辟空间大小的调整,第一个参数是原空间的首地址,第二个是调整后的新大小。

    realloc在申请空间时有两种情况,
    第一种:原空间后有足够大小的空间,就会直接在后面进行追加到size大小,原来数据不变,返回值还是原空间起始地址
    第二种:原空间后无足够大小的空间,会在堆上重新找一块满足需求的空间来使用,然后将原空间数据搬移到新空间,free掉旧空间,再返回新空间的起始地址。


我们最常用到的还是malloc,他在底层是如何实现的呢??

不同的操作系统对malloc的实现略有不同,这里我们简单说一说linux下的实现原理: ![这里写图片描述](https://img-blog.csdn.net/20180819091103129?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NoaWRhbnRvbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) Linux维护一个break指针,这个指针指向堆空间的某个地址。从堆起始地址到break之间的地址空间为映射好的,可以供进程访问;而从break往上,是未映射的地址空间,如果访问这段空间则程序会报错。我们用malloc进行内存分配就是从break往上进行的。 获得了break指针的位置也就得到了堆内存申请的起始地址 malloc实际上是将可用空间用一个空闲链表连接起来,若用户申请空间,就遍历该链表,找到第一个满足条件的空闲块,将其进行拆分,返回合适大小的空间给用户,将剩下的部分链接到链表中。当调用free释放空间时,会把这块空间连接到空闲链表上。到最后,该空闲链就会被切成很多的小内存块,一旦用户申请一块较大的空间,空闲链中的空间大小都无法满足需求,malloc会申请延时,对空闲链表进行检查,内存重新整理,把相邻的小片段合并成大的空闲块。

搜索空闲块最常见的算法有:首次适配,下一次适配,最佳适配。
首次适配:第一次找到足够大的内存块就分配,这种方法会产生很多的内存碎片。
下一次适配:也就是说等第二次找到足够大的内存块就分配,这样会产生比较少的内存碎片。
最佳适配:对堆进行彻底的搜索,从头开始,遍历所有块,使用数据区大小大于size且差值最小的块作为此次分配的块。

malloc中实际调用了brk()、sbrk()函数
//函数原型:
#include<unistd.h>
int brk(void * addr); 
void * sbrk(intptr_t increment);
这两个函数都用来改变 “program break” (程序间断点)的位置,改变数据段长度(Change data segment size),实现虚拟内存到物理内存的映射。 brk()函数直接修改有效访问范围的末尾地址实现分配与回收。 **注意:** 当使用 malloc() 分配过大的空间,malloc 不再从堆中分配空间,而是使用 mmap() 这个系统调用从映射区寻找可用的内存空间。 1.当开辟的空间小于128k时,调用brk()函数,malloc的底层实现是系统调用函数brk(),其主要移动指针_enddata来开辟空间、 2.当开辟的空间大于128k时,mmap()系统调用函数来在虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空间来开辟。

mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。mmap在用户空间映射调用系统中作用很大


一些常见的动态内存错误:

1.对NULL指针解引用
2.对动态开辟空间进行越界访问
3.非动态开辟空间使用free进行释放
4.在使用free释放一块动态开辟的空间只释放了部分
5.一块空间多次释放
6.忘记释放—->内存泄露


什么是内存泄露??

指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

python中就没有此类问题,因为python自带垃圾回收机制。

C/C++中主要有两种内存泄露:

  • 堆内存泄漏(Heap leak)。对内存指的是程序运行中根据需要分配通过malloc,realloc new等从堆中分配的一块内存,再是完成后必须通过调用对应的 free或者delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak.
  • 系统资源泄露(Resource Leak).主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。
C/C++中如何检测内存泄露??
  • 使用一些软件测试工具进行检测
  • 最直接的方式还是打开windows下的任务管理器查看“内存使用”和”虚拟内存大小”进行检测。

总之,在使用动态内存申请时还是要相当谨慎,使用完记得要及时free。

猜你喜欢

转载自blog.csdn.net/shidantong/article/details/81835694