内存学习——堆(heap)

目录

    • 一、概念
    • 二、自定义malloc函数
    • 三、Debug运行
    • 四、heap_4简单分析
      • 4.1 heap管理链表结构体
      • 4.2 堆初始化
      • 4.3 malloc使用
      • 4.4 free使用

一、概念

内存分为堆和栈两部分:

栈(Stack)是一种后进先出(LIFO)的数据结构,用于存储函数的调用栈、内存的分配操作、表达式求值的临时变量以及与程序中的控制流相关的数据。每当程序执行函数调用、变量声明或其他类型的操作时,都会在栈中添加一个栈帧(Stack Frame),用于存储函数的执行环境。

栈内存:主要存放函数地址、函数参数、局部变量等,空间较小,远小于堆内存,所以常有栈溢出错误。它由系统自动申请和回收,只由单线程使用,访问速度快,是连续内存,集中在内存块附近。

堆(Heap)则是用于分配程序中动态数据结构的内存空间,它的生命周期不由程序的函数调用栈管理,因此堆空间通常会被程序员直接管理。堆内存通常是一个可以被看做是一棵树的数组对象,它满足堆的性质:堆中某个节点的值总是不大于或不小于其父节点的值。堆空间为程序提供了极为灵活的空间分配和管理手段,既可以手动管理,也可以交由垃圾回收机制自动管理。

堆内存:主要存放new出来的对象和malloc申请的空间,空间大。它由程序分配,使用new或malloc申请,使用free或delete释放。堆内存中的实体数据地址都存储在栈变量中(即引用),以便能够高速访问。

总的来说,堆和栈在程序中都扮演着重要的角色。栈是一种高效的内存结构,用于存放基础数据类型和引用类型的变量,大大简化内存的管理,提高了程序的执行效率。堆空间则为程序提供了极为灵活的空间分配和管理手段。

被管理的内存称为堆,未被管理的内存称为栈:

  • 堆, heap,就是一块空闲的内存,需要提供管理函数
    • malloc:从堆里划出一块空间给程序使用
    • free:用完后,再把它标记为"空闲"的,可以再次使用
  • 栈, stack,函数调用时局部变量保存在栈中,当前程序的环境也是保存在栈中
    • 可以从堆中分配一块空间用作栈

在这里插入图片描述

二、自定义malloc函数

代码:

char heap_buf[1024]; //自定义1024字节内存的数组,模拟堆
int pos = 0; //指向堆数组可用空间的首地址void *my_malloc(int size) //自定义malloc函数
{int old_pos = pos; //记录开辟空间的首地址pos += size; //malloc的空间大小return &heap_buf[old_pos]; //返回开辟空间的首地址
}void my_free(void *buf) //可用自定义malloc函数,但是无法自定义free函数,后面分析原因
{/* err */
}int main(void)
{char ch = 65; // char ch = 'A';int i;char *buf = my_malloc(100); //使用自定义的malloc函数在自定义堆数组中开辟100字节空间for (i = 0; i < 26; i++)buf[i] = 'A' + i; //在新开辟的空间中依次填入ABC……XYZreturn 0;
}

三、Debug运行

1、查看heap_buf的首地址

在这里插入图片描述

2、查看malloc的buf首地址与heap_buf的首地址相同

在这里插入图片描述

3、多运行几次,ABCDE字母填入buf空间里,在heap_buf中也可以看到ABCDE

在这里插入图片描述

四、heap_4简单分析

4.1 heap管理链表结构体

对堆的管理意味着需要有一个链表结构体对空闲的堆空间和已使用的堆空间进行管理。

参考Heap_4的堆管理链表结构体:

typedef unsigned long size_t;/* Define the linked list structure.  This is used to link free blocks in order* of their memory address. */
typedef struct A_BLOCK_LINK
{struct A_BLOCK_LINK * pxNextFreeBlock; /*<< The next free block in the list. */size_t xBlockSize;                     /*<< The size of the free block. */
} BlockLink_t;

链表结构体包含2部分:

  • 下一个空闲块
  • 当前块的size

4.2 堆初始化

当第一次使用malloc时,会对Heap进行初始化:

static void prvHeapInit( void ) /* PRIVILEGED_FUNCTION */
{BlockLink_t * pxFirstFreeBlock;uint8_t * pucAlignedHeap;portPOINTER_SIZE_TYPE uxAddress;size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;/* 确保堆从正确对齐的边界开始。 */uxAddress = ( portPOINTER_SIZE_TYPE ) ucHeap;if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 ){uxAddress += ( portBYTE_ALIGNMENT - 1 );uxAddress &= ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK );xTotalHeapSize -= uxAddress - ( portPOINTER_SIZE_TYPE ) ucHeap;}pucAlignedHeap = ( uint8_t * ) uxAddress;/* xStart用于保存指向空闲块列表中第一项的指针。void强制转换用于防止编译器警告。 */xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;xStart.xBlockSize = ( size_t ) 0;/* pxEnd用于标记空闲块列表的末尾,并插入到堆空间的末尾。 */uxAddress = ( ( portPOINTER_SIZE_TYPE ) pucAlignedHeap ) + xTotalHeapSize;uxAddress -= xHeapStructSize;uxAddress &= ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK );pxEnd = ( BlockLink_t * ) uxAddress;pxEnd->xBlockSize = 0;pxEnd->pxNextFreeBlock = NULL;/* 首先,有一个空闲块,它的大小是占用整个堆空间,减去pxEnd占用的空间。 */pxFirstFreeBlock = ( BlockLink_t * ) pucAlignedHeap;pxFirstFreeBlock->xBlockSize = ( size_t ) ( uxAddress - ( portPOINTER_SIZE_TYPE ) pxFirstFreeBlock );pxFirstFreeBlock->pxNextFreeBlock = pxEnd;/* 只有一个块存在——它覆盖了整个可用的堆空间。 */xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
}

通过代码可以看到,对Heap初始化时会创建一个xStart和*pxEnd :

//heap_4中定义:
/* 创建两个列表链接来标记列表的开始和结束。 */
PRIVILEGED_DATA static BlockLink_t xStart;
PRIVILEGED_DATA static BlockLink_t * pxEnd = NULL;
//heap_2中定义:
/* 创建两个列表链接来标记列表的开始和结束。 */
PRIVILEGED_DATA static BlockLink_t xStart, xEnd;

heap_2中的xStart, xEnd,用2个结构体代表头尾,不占用堆空间;

heap_4中是xStart, *pxEnd = NULL,pxEnd与heap_2中xEnd不一样,pxEnd占用了堆空间。这样做能够适配heap_5,heap_5支持多块不连续的内存合并,使用pxEnd链表,可以直接将pxEnd指向下一个非连续堆。

4.3 malloc使用

假设malloc开辟了3块空间,第一块空间从heap头开始100字节,第二块空间接着第一块空间100字节,第三块空间接着第二块50字节,最后释放了第二块,则示意图如下:

在这里插入图片描述

对照代码分析:

void * pvPortMalloc( size_t xWantedSize )
{BlockLink_t * pxBlock;BlockLink_t * pxPreviousBlock;BlockLink_t * pxNewBlockLink;void * pvReturn = NULL;size_t xAdditionalRequiredSize;vTaskSuspendAll();{/* 如果这是第一次调用malloc,那么堆将需要初始化来设置空闲块列表。 */if( pxEnd == NULL ){prvHeapInit();}else{mtCOVERAGE_TEST_MARKER();}if( xWantedSize > 0 ){/* 所需的大小必须增加,这样除了请求的字节数之外,它还可以包含BlockLink_t结构。为了对齐,可能还需要一些额外的增量。 */xAdditionalRequiredSize = xHeapStructSize + portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK );if( heapADD_WILL_OVERFLOW( xWantedSize, xAdditionalRequiredSize ) == 0 ){xWantedSize += xAdditionalRequiredSize;}else{xWantedSize = 0;}}else{mtCOVERAGE_TEST_MARKER();}/* 检查我们尝试分配的块大小是否太大,以至于设置了顶部位。BlockLink_t结构的块大小成员的顶部位用于确定谁拥有该块-应用程序或内核,因此它必须是空闲的。 */if( heapBLOCK_SIZE_IS_VALID( xWantedSize ) != 0 ){if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) ){/* 从起始(最低地址)块开始遍历列表,直到找到一个足够大的块。 */pxPreviousBlock = &xStart;pxBlock = xStart.pxNextFreeBlock;while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) ){pxPreviousBlock = pxBlock;pxBlock = pxBlock->pxNextFreeBlock;}/* 如果到达终点标记,则没有找到足够大小的块。 */if( pxBlock != pxEnd ){/* 返回指向的内存空间-在开始时跳过BlockLink_t结构。 */pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );/* 该块正在返回使用,因此必须从空闲块列表中删除。 */pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;/* 如果块大于要求,则可以将其分成两个。 */if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE ){/* 这个块将被分成两部分。根据请求的字节数创建一个新的块。void强制转换用于防止编译器发出字节对齐警告。 */pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );configASSERT( ( ( ( size_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );/* 计算从单个块分割出的两个块的大小。 */pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;pxBlock->xBlockSize = xWantedSize;/* 将新块插入空闲块列表中。 */prvInsertBlockIntoFreeList( pxNewBlockLink );}else{mtCOVERAGE_TEST_MARKER();}xFreeBytesRemaining -= pxBlock->xBlockSize;if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining ){xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;}else{mtCOVERAGE_TEST_MARKER();}/* 块正在被返回——它被应用程序分配和拥有,没有“下一个”块。 */heapALLOCATE_BLOCK( pxBlock );pxBlock->pxNextFreeBlock = NULL;xNumberOfSuccessfulAllocations++;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}traceMALLOC( pvReturn, xWantedSize );}( void ) xTaskResumeAll();#if ( configUSE_MALLOC_FAILED_HOOK == 1 ){if( pvReturn == NULL ){vApplicationMallocFailedHook();}else{mtCOVERAGE_TEST_MARKER();}}#endif /* if ( configUSE_MALLOC_FAILED_HOOK == 1 ) */configASSERT( ( ( ( size_t ) pvReturn ) & ( size_t ) portBYTE_ALIGNMENT_MASK ) == 0 );return pvReturn;
}

4.4 free使用

假设使用free释放内存时,输入的参数pv是Heap中实际使用地址的首地址,但是malloc的空间除了实际使用的部分外还有一个头部的链表结构体,因此需要将pv地址向前移动xHeapStructSize,把malloc开辟空间的头部链表结构体与实际使用部分一同释放并插入到空闲Heap中:

void vPortFree( void * pv )
{uint8_t * puc = ( uint8_t * ) pv;BlockLink_t * pxLink;if( pv != NULL ){/* 被释放的内存在其前面有一个BlockLink_t结构。 */puc -= xHeapStructSize;/* 这种类型转换是为了防止编译器发出警告。 */pxLink = ( void * ) puc;configASSERT( heapBLOCK_IS_ALLOCATED( pxLink ) != 0 );configASSERT( pxLink->pxNextFreeBlock == NULL );if( heapBLOCK_IS_ALLOCATED( pxLink ) != 0 ){if( pxLink->pxNextFreeBlock == NULL ){/* 该块被返回到堆中——它不再被分配。 */heapFREE_BLOCK( pxLink );#if ( configHEAP_CLEAR_MEMORY_ON_FREE == 1 ){( void ) memset( puc + xHeapStructSize, 0, pxLink->xBlockSize - xHeapStructSize );}#endifvTaskSuspendAll();{/* 将此块添加到空闲块列表中。 */xFreeBytesRemaining += pxLink->xBlockSize;traceFREE( pv, pxLink->xBlockSize );prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );xNumberOfSuccessfulFrees++;}( void ) xTaskResumeAll();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}
}

其中heap4中把相邻的空闲内存合并为一个更大的空闲内存是在prvInsertBlockIntoFreeList函数中实现的:

static void prvInsertBlockIntoFreeList( BlockLink_t * pxBlockToInsert ) /* PRIVILEGED_FUNCTION */
{BlockLink_t * pxIterator;uint8_t * puc;/* 遍历列表,直到发现一个空闲块的下一个空闲块指针地址比插入的块的地址高,即得到比插入块地址小的相邻空闲块。问题:假设释放heap中地址最高的一段时,所有的空闲块地址都比要插入的块的地址低,此时该如何执行?*/for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock ){/* 这里什么都不用做,只需要迭代到正确的位置。 */}/* 插入的块和比其地址低的相邻块是否构成连续的内存块? 体现相邻空闲空间插入思想*/puc = ( uint8_t * ) pxIterator;if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert ){pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;pxBlockToInsert = pxIterator;}else{mtCOVERAGE_TEST_MARKER();}/* 插入的块和比其地址高的相邻块是否构成一个连续的内存块? 体现相邻空闲空间插入思想*/puc = ( uint8_t * ) pxBlockToInsert;if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock ){if( pxIterator->pxNextFreeBlock != pxEnd ){/* 把两个块组成一个大的块。 */pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;}else{pxBlockToInsert->pxNextFreeBlock = pxEnd;}}else{pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;}/* 如果被插入的块和比其地址低的相邻块不连续,而和比其地址高的相邻块连续,因此和比其地址低的相邻块的pxNextFreeBlock指针应该由原本指向和比其地址高的相邻块变为指向被插入的块 */if( pxIterator != pxBlockToInsert ){pxIterator->pxNextFreeBlock = pxBlockToInsert;}else{mtCOVERAGE_TEST_MARKER();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/213897.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flutter自定义下拉选择框dropDownMenu

利用PopupMenuButton和PopupMenuItem写了个下拉选择框&#xff0c;之所以不采用系统的&#xff0c;是因为自定义的更能适配项目需求&#xff0c;话不多说&#xff0c;直接看效果 下面直接贴出代码、代码中注释写的都很清楚&#xff0c;使用起来应该很方便&#xff0c;如果有任何…

Navicat 技术指引 | 适用于 GaussDB 分布式的数据生成功能

Navicat Premium&#xff08;16.3.3 Windows 版或以上&#xff09;正式支持 GaussDB 分布式数据库。GaussDB 分布式模式更适合对系统可用性和数据处理能力要求较高的场景。Navicat 工具不仅提供可视化数据查看和编辑功能&#xff0c;还提供强大的高阶功能&#xff08;如模型、结…

得帆云助力容百科技构建CRM系统,实现LTC全流程管理

宁波容百新能源科技股份有限公司 宁波容百新能源科技股份有限公司&#xff08;以下简称“容百科技”&#xff09;于2014年9月建立&#xff0c;是高科技新能源材料行业的跨国型集团公司。专业从事锂电池正极材料的研发、生产和销售&#xff0c;于2019年登陆上交所科创板&#x…

Docker 部署 2FAuth 服务

拉取最新版本的 2FAuth 镜像&#xff1a; $ sudo docker pull 2fauth/2fauth:latest在本地预先创建好 2fauth 目录, 用于映射 2FAuth 容器内的 /2fauth 目录。 使用以下命令, 在 前台 运行 2FAuth 容器: $ sudo docker run -it --rm --name 2fauth -p 10085:8000/tcp -v /ho…

阻抗控制下机器人接触刚性环境振荡不稳定进行阻抗调节

在阻抗控制下&#xff0c;当机器人接触刚性环境时&#xff0c;可能会出现振荡不稳定的情况。这可以通过调整机器人的阻抗参数来进行调节。 阻抗接触 阻抗参数中的质量、阻尼和刚度都会对机器人控制系统的性能和稳定性产生重要影响。质量主要影响系统的惯性&#xff0c;从而影响…

打破常规思维:Scrapy处理豆瓣视频下载的方式

概述 Scrapy是一个强大的Python爬虫框架&#xff0c;它可以帮助我们快速地开发和部署各种类型的爬虫项目。Scrapy提供了许多方便的功能&#xff0c;例如请求调度、数据提取、数据存储、中间件、管道、信号等&#xff0c;让我们可以专注于业务逻辑&#xff0c;而不用担心底层的…

如何用Qt配置git项目并上传Gitee

1.进入到Qt项目文件夹内&#xff0c;打开 “Git Bash Here” 2.初始化&#xff0c;在“Git Bash Here”中输入 git init 3.加入所有文件&#xff0c;在“Git Bash Here”中输入 git add . (需要注意&#xff0c;git add 后面还有一个点) 4.添加备注&#xff0c;git com…

Hive数据库系列--Hive数据类型/Hive字段类型/Hive类型转换

文章目录 一、Hive数据类型1.1、数值类型1.2、字符类型1.3、日期时间类型1.4、其他类型1.5、集合数据类型1.5.1、Struct举例1.5.2、Array举例1.5.3、Map举例 二、数据类型转换2.1、隐式转换2.2、显示转换 三、字段类型的使用3.1、DECIMAL(precision&#xff0c;scale) 本章主要…

linux驱动开发——内核调试技术

目录 一、前言 二、内核调试方法 2.1 内核调试概述 2.2 学会分析内核源程序 2.3调试方法介绍 三、内核打印函数 3.1内核镜像解压前的串口输出函数 3.2 内核镜像解压后的串口输出函数 3.3 内核打印函数 四、获取内核信息 4.1系统请求键 4.2 通过/proc 接口 4.3 通过…

ES 如何将国际标准时间格式进行格式化与调整时区

需求&#xff0c;日志收集的时候&#xff0c;时间格式是国际标准时间格式。形如yyyy-MM-ddTHH:mm:ss.SSS。 &#xff08;2023-12-05T02:45:50.282Z&#xff09;这个时区也不对&#xff0c;那如何将此类型的时间&#xff0c;进行格式化呢&#xff1f; 本篇文章体统一个案例&…

STM32F103的启动过程及BootLoader作用

1.STM32的启动过程 1.1 复位后的启动模式选择 我们知道的复位方式有三种&#xff1a;上电复位&#xff0c;硬件复位和软件复位。当产生复位&#xff0c;并且离开复位状态后&#xff0c;CM3 内核做的第一件事就是读取下列两个32 位整数的值&#xff1a; &#xff08;1&#xff0…

大数据技术6: 大数据技术栈

前言&#xff1a;大数据相关的技术名词特别多&#xff0c;这些技术栈之间的关系是什么&#xff0c;对初学者来说很难找到抓手。我一开始从后端转大数据的时候有点懵逼&#xff0c;整体接触了一遍之后才把大数据技术栈给弄明白了。 一、大数据技术栈 做大数据开发&#xff0c;无…

「Python编程基础」第7章:字符串操作

文章目录 一、回顾二、新手容易踩坑的引号三、转义字符四、多行字符串写法五、注释六、字符串索引和切片七、字符串的in 和 not in八、字符串拼接九、转换大小写十、合并字符串join()十一、分割字符串split()十二、字符串替换 replace()十三、字符串内容判断方法十四、字符串内…

使用MetaMask + Ganache搭建本地私有网络并实现合约部署与互动

我使用Remix编写合约&#xff0c;MetaMask钱包工具和Ganache搭建了一个私有网络&#xff0c;并且实现了合约的部署和互动。 在前面的博客中提到了 Remix在线环境及钱包申请 以及 Solidity的基本语法 &#xff0c;没看过的小伙伴可以点击链接查看一下&#xff0c;都是在本专栏下…

概率测度理论方法(第 2 部分)

一、说明 欢迎回到这个三部曲的第二部分&#xff01;在第一部分中&#xff0c;我们为测度论概率奠定了基础。我们探索了测量和可测量空间的概念&#xff0c;并使用这些概念定义了概率空间。在本文中&#xff0c;我们使用测度论来理解随机变量。 作为一个小回顾&#xff0c;在第…

stm32 使用18B20 测试温度

用18b20 测试温度是非常常用的&#xff0c;不过18B20的调试不是这么容易的&#xff0c;有些内容网上很多的&#xff0c;不再重复说了&#xff0c;我先把波形说一下&#xff0c;再说程序部分&#xff1a; 整个都温度数据的顺序是&#xff1a; 1.700uS的低电平复位并测试18B20的…

如何用Python编写俄罗斯方块Tetris游戏?

在本文中&#xff0c;我们将用Python代码构建一个令人惊叹的项目&#xff1a;俄罗斯方块游戏。在这个项目中&#xff0c;我们将使用pygame库来构建游戏。要创建此项目&#xff0c;请确保您的系统中安装了最新版本的Python。让我们开始吧&#xff01; Pygame是一组跨平台的Pyth…

基于Python+Django+mysql图书管理系统

基于PythonDjangomysql图书管理系统 一、系统介绍二、功能展示三、其它系统四、获取源码 一、系统介绍 程序开发软件&#xff1a;Pycharm 数据库&#xff1a;mysql 采用技术&#xff1a; Django(一个MVT框架&#xff0c;类似Java的SSM框架) 人生苦短&#xff0c;我用Python&a…

构建外卖系统:使用Django框架

在当今数字化的时代&#xff0c;外卖系统的搭建不再是什么复杂的任务。通过使用Django框架&#xff0c;我们可以迅速建立一个强大、灵活且易于扩展的外卖系统。本文将演示如何使用Django构建一个简单的外卖系统&#xff0c;并包含一些基本的技术代码。 步骤一&#xff1a;安装…

Java、JDK、JRE、JVM

Java、JDK、JRE、JVM 一、 Java 广义上看&#xff0c;Kotlin、JRuby等运行于Java虚拟机上的编程语言以及相关的程序都属于Java体系的一员。从传统意义上看&#xff0c;Java社区规定的Java技术体系包括以下几个部分&#xff1a; Java程序设计语言各种硬件平台上的Java虚拟机实…