首页 > 编程语言 >C/C++调试---堆数据结构

C/C++调试---堆数据结构

时间:2025-01-05 11:03:06浏览次数:3  
标签:ptmalloc 盒子 字节 C++ --- 内存 大小 数据结构 空闲

堆数据结构

因为C/C++语言赋予程序员通过引用和指针来操纵内存对象的最大自由,所以毫不奇怪的是这些程序中的大多数bug都与错误的内存访问有关。

根据错误发生的位置是栈还是堆,内存错误可分为两种:栈错误堆错误

栈是分配给给一个独立的控制流(线程)的来纳许内存区域,用于追踪线程的动态调用链。每个函数在进入时会分配一个栈帧,即一个内存块。

主线程(进程创建时的第一个线程)的大小由生成这个进程的shell的栈大小ulimit设置决定的。

堆是程序代码显式创建和释放动态数据对象的内存区域。堆服务于同一进程下的所有线程,它的地址通常紧接在可执行文件的全局数据段之后。

全局数据

存放在data节(初始化的数据)或者.bss节(未初始化的数据)。生命周期与包含它的模块相同。

内存管理器

内存管理器记录着每个堆内存块的大小和状态,即内存块是空闲的还是在使用中。这个简要信息常常可以帮助我们缩小一个棘手问题的范围,并提供强有力的证据来证明或者证伪一个理论。

ptmalloc

通过两个关键的数据结构来管理堆内存块:边界标签和盒子。它们被声明在malloc/malloc.c中,源代码可以在GNU C运行库glibc里面找到。

边界标签

边界标签也称块标签,在ptmalloc中叫做malloc_chunk,每个内存块都有,用来记录当前内存块的大小和状态。
在这里插入图片描述
ptmalloc边界标签
上图灰色框的是边界标签。

size大小字段放在内存块的开始位置,它最低的两个比特分别表示当前块和前一个内存块是空闲的还是在使用中。
使用中的块和空闲块的边界标签的内容和大小是不一样的。正在使用中的块标签只使用了大小字段,但是空闲的内存块的标签使用了结构体malloc_chunk所有的4个字段。

prev-size字段是放置在空闲内存块末尾的另一个大小字段,目的是让内存管理器可以由地址低端向上合并地址高端的空闲块。

fd和bk,指向其他空闲块,ptmalloc会使用它们来构建空闲块的双链表。当应用程序请求一个新的内存块时,ptmalloc会搜索这个链表来寻找最合适的空闲块。

因为标签数据结构的存在,一个ptmalloc管理的最小内存块不会小于结构体malloc_chunk的大小,对于64位应用程序来说,是32字节。

被分配出去的内存块的实际空间消耗仅仅只有8字节,也就是size字段使用的空间。不同于空闲块,使用中的内存块不需要双链表的下一个和前一个指针,同时它把末尾的紧邻块的prev-size字段给占用了,因为当它在使用的时候,我们不需要合并这个块。

盒子

所有的空闲块被收集到盒子bins里,这些盒子被实现为双向链表数组,并按块大小进行索引。这个数组被声明为ptmalloc管理堆的顶层元数据结构体malloc_state的数据成员:
在这里插入图片描述
ptmalloc空闲块盒子
盒子中空闲块的大小随着数组索引的增大而增大。盒子之间的间距是仔细选择过的。因为大部分用户请求都是小块的内存,从24字节到512字节的盒子都是精确的大小,以8字节隔开。这些盒子被叫作小盒子(Small Bin)。剩下的盒子以大小的对数间隔。

上图显示24字节大小的盒子有3个空闲块,40字节大小的盒子有1个空闲块,576字节大小的盒子有两个空闲块,大小在512字节和576字节之间。盒子的空闲块大小大于512字节的,按大小排好序并用最好匹配方法分配。

  • 请求内存

当接收到用户请求时,分配器首先检查并调整请求块的大小。如果有必要,取整到不小于最小块的大小(64位程序是32字节),另外为了满足对齐要求可能会增加一些填充。如果调整过的请求块大小落在精确大小的小盒子(24~512字节)上,那么计算得到对应的数组索引并检查其中的空闲链表。
如果链表具有空闲块,那么会移除链表头部空闲块并返回给用户。因为链表所有的空闲块都是同样大小,所以没有必要遍历链表。
如果链表是空的,那么下一个比较大的缓存着的盒子就会被检查。如果有一个空闲块大于请求的大小,那么它会被分割成两部分:一部分满足请求并返回给用户;另外一部分叫作剩余块,会放到相应盒子中以备将来使用。
如果下一个盒子没有空闲块,分配器会继续搜索更大的盒子,直到合适的空闲块被找到。
如果所有的盒子都被用光并且没有可以满足申请大小的候选内存块,ptmalloc会转向系统的VMM(虚拟机管理器)来获取一大块内存,并分割成两个内存块:一个返回给用户,一个被存入相应的盒子里。

  • 释放内存

当内存块被用户释放时,分配器从镶嵌的块标签中获取它的大小。如果当前内存块的前面和后面也是空闲的,ptmalloc会试图合并它们,此时前一个和后一个空闲块会从它们相应的双链表中移除。合并后的空闲块会被放到未排序的链表列表中。

补充

ptmalloc还采用了其他一些有趣的技术来提高性能和减少内存消耗。

  • 快速盒子(Fast Bin)
    保存在快速盒子中的最大空闲块更小,默认值是80字节。如果用户释放的内存块的大小小于快速盒子的最大内存块的大小,它会被直接放入对应的快速盒子里,且不更改它的块标签,这一点非常重要。即使可以合并,它也不会跟周围的空闲块合并。当新的请求到来时,在检查常规的盒子前,会先检查快速盒子。如果有合适的,这个缓存的内存块会立即返回。同样的它的标签不需要被调整。在这种情况下,请求可以尽可能快地被满足。这个算法在经常需要构建和析构小对象的C++程序中工作得很好。为了避免碎片化,快速盒子的空闲块在一些条件下会被合并。如果一个请求大于小盒子的最大块的大小或者没有空闲的块可以满足小的请求,在快速盒子中的内存块就会被处理,也就是先与邻近的空闲块合并然后放到对应的常规盒子里。

  • 未排序chunks
    这个盒子里包含了暂时的最近内存分割带来的剩余部分或者是用户刚刚释放的空闲块。如果快速盒子和小盒子都不能够满足一个请求,那么在未排序的chunks里的空闲块会一个接一个地被考虑。如果找到一个匹配的空闲块,那么这个块会被返回给用户;否则,它会被放入常规盒子里。当搜索遍历完所有空闲块后,它们会重新分配到合适的盒子里。
    这种对最近空闲块的处理是为了提高内存的局部性和性能,因为程序往往会申请一组相关的内存块,比如一个数组的所有单元结构体,分配器采用上述考虑提高了数组内存地址顺序排列的可能性。

  • mmap
    如果用户请求的大小超出了一个可调整的阈值(默认是128KB),并且ptmalloc无法找到一个足够大的缓存内存块来满足该请求,那么它会从VMM分配一块匿名的mmaped内存并直接返回给用户。为了减少内存碎片和程序占用的系统内存,当这样的内存块被用户释放时,ptmalloc不会缓存它,而是直接返回给VMM。这在很大程度上保证了进程在运行了很长时间后,还可以保持低内存占用。

tmalloc

TCMalloc(Thread-Caching Malloc)是一种高性能的内存管理器,由Google开发。它是一个优化过的内存分配器,旨在为C和C++应用程序提供更快、更高效的内存分配和释放。

利用堆元数据

堆元数据告诉我们应用程序数据对象的基本状态,它可以为查找内存损坏的原因提供重要线索。

在ptmalloc中,每个内存块之前都有一个名为malloc_chunk的小数据结构,即块标签。如果用户输入一个由函数malloc返回的有效地址,则内存块的标签正好在这个地址的前面。块标签的size字段说明当前块的大小。为了知道当前块是在使用中还是空闲的,需要计算下一个块的地址。当前块的状态编码在下一个块的size字段中。

https://github.com/yanqi27
参考: 高效C/C++调试

标签:ptmalloc,盒子,字节,C++,---,内存,大小,数据结构,空闲
From: https://blog.csdn.net/haolindy/article/details/144939948

相关文章

  • C/C++调试---调试符号与调试器
    调试符号与调试器调试符号调试符号由编译器生成,与相关的机器代码、全局数据对象等一同产生。链接器会收集并组织这些符号,将他们写入可执行文件的调试部分,或存储到一个单独文件中。概览全局函数和变量源文件和行信息为了优化程序性能,编译器可能会对源代码进行位移,情......
  • 【故障诊断】【pytorch】基于EMD-CNN故障分类的轴承故障诊断研究[西储大学数据](Python
       ......
  • Error occurred prerendering page "/_not-found".(Next.js 15)
    我们需要更新UserProfile.tsx组件,改用Next.js的Link组件而不是react-router-dom的Link组件。以下是解决方法:这样可以确保组件更好地适应Next.js的框架,避免不兼容的问题。#错误的代码'useclient'importReactfrom'react'import{Box,Avatar,Typography,......
  • [数据结构学习笔记4] 链表
    链表(LinkedLists)和数组类似,链表也是用来存放一组数据。和数组不一样的是,链表存储不需要连续的内存位置,一个链表由很多节点组成,节点与节点间通过一个next指针关联。图示:NodeValue/DataNext 链表操作:查找一个值:通过链表的next指针一直往下跳直到:1.找到了想......
  • Cross-modal Information Flow in Multimodal Large Language Models
    本文是LLM系列文章,针对《Cross-modalInformationFlowinMultimodalLargeLanguageModels》的翻译。多模态大型语言模型中的跨模态信息流摘要1引言2相关工作3MLLM中的信息流跟踪4实验设置5不同模态对最终预测的贡献6语言和视觉信息如何集成的?7最终答......
  • 数据结构(排序算法)
    插入排序插入排序(InsertionSort)是一种简单直观的排序算法,其原理可以简述如下:1.分已排序区间和未排序区间:将数组分为已排序区间和未排序区间。初始时,已排序区间只包含数组的第一个元素,而未排序区间包含除第一个元素之外的所有元素。2.依次将未排序区间中的元素插入到已......
  • 数据结构理论篇(期末突击)
    找往期文章包括但不限于本期文章中不懂的知识点:个人主页:我要学编程(ಥ_ಥ)-CSDN博客所属专栏: 学校课程突击下面均是为了应付学校考试所用,如果有涉及部分知识点下面未说明,可以去我的数据结构专栏看看或者自行在网上查阅资料。 以下所有知识均是阅读大话数据结构所得。如......
  • 【9606】基于springboot+vue的职称评审管理系统-lw
    作者主页:Java码库主营内容:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。收藏点赞不迷路 关注作者有好处文末获取源码项目描述不管是从事哪个行业、对于职称是对一个对个人的最高荣誉,有通过科技手段、农业......
  • 企业面试题-聚水潭
    自我介绍1.对着项目问2.list和字典哪个性能高?for循环下哪个性能高?为啥?3.EF和EFCore的区别,性能上有哪些区别,哪个性能高?如何优化EF/EFCore的性能?4.如何看.netframework和.netcore?5.framework和core哪个性能好?为啥?6.sql性能优化,如何优化in/notin这类关键字的语句?7.如何判......
  • 7-406 三七二十一
    某天,诺诺看到三七二十一(3721)数,觉得很神奇,这种数除以3余2,而除以7则余1。例如8是一个3721数,因为8除以3余2,8除以7余1。现在给出两个整数a、b,求区间[a,b]中的所有3721数,若区间内不存在3721数则输出none。输入格式:首先输入一个正整数T,表示测试数据的组数,然后是T组测试数据。每组测......