内存学习——堆(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.mzph.cn/news/211819.shtml

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

相关文章

AVFormatContext封装层:理论与实战

文章目录 前言一、封装格式简介1、FFmpeg 中的封装格式2、查看 FFmpeg 支持的封装格式 二、API 介绍三、 实战 1&#xff1a;解封装1、原理讲解2、示例源码 13、运行结果 14、示例源码 25、运行结果 2 四、 实战 2&#xff1a;转封装1、原理讲解2、示例源码3、运行结果 前言 A…

文章解读与仿真程序复现思路——电力系统自动化EI\CSCD\北大核心《考虑电力-交通交互的配电网故障下电动汽车充电演化特性》

这个标题涉及到电力系统、交通系统和电动汽车充电的复杂主题。让我们逐步解读&#xff1a; 考虑电力-交通交互的配电网故障&#xff1a; 电力-交通交互&#xff1a; 指的是电力系统和交通系统之间相互影响、相互关联的关系。这可能涉及到电力需求对交通流量的影响&#xff0c;反…

回溯算法之N皇后

一 什么是回溯算法 回溯算法&#xff08;Backtracking Algorithm&#xff09;是一种用于解决组合优化问题的算法&#xff0c;它通过逐步构建候选解并进行验证&#xff0c;以寻找所有满足特定条件的解。回溯算法通常应用于在给定约束条件下枚举所有可能解的问题&#xff0c;如…

Git—文件添加查看删除修改

目录 1.添加文件—场景一 2.查看.git文件 3.添加文件—场景三 4.修改文件 5.版本回退 6.撤销修改 7.删除文件 1.添加文件—场景一 在包含.git的目录下新建⼀个ReadMe文件&#xff0c;我们可以使用 git add 命令可以将文件添加到暂存 区&#xff1a; ●添加一个或多个文…

Matlab数学建模算法之小波神经网络详解

&#x1f517; 运行环境&#xff1a;Matlab &#x1f6a9; 撰写作者&#xff1a;左手の明天 &#x1f947; 精选专栏&#xff1a;《python》 &#x1f525; 推荐专栏&#xff1a;《算法研究》 &#x1f510;#### 防伪水印——左手の明天 ####&#x1f510; &#x1f497; 大家…

存储成本降71%,怪兽充电历史库迁移OceanBase

怪兽充电作为共享充电宝第一股&#xff0c;业务增长迅速&#xff0c;以至于业务架构不停地增加组件。在验证 OceanBase 可以简化架构并带来更大的业务价值后&#xff0c;首次尝试在历史库中使用 OceanBase 替代 MySQL&#xff0c;存储成本降低 71%。本文为怪兽充电运维架构部王…

Docker 入门

Docker 入门 基础 不同操作系统下其安装包、运行环境是都不相同的&#xff01;如果是手动安装&#xff0c;必须手动解决安装包不同、环境不同的、配置不同的问题 而使用Docker&#xff0c;这些完全不用考虑。就是因为Docker会自动搜索并下载MySQL。注意&#xff1a;这里下载…

【C++】输入输出流 ⑥ ( cout 标准输出流对象 | cout 常用 api 简介 | cout.put(char c) 函数 )

文章目录 一、cout 标准输出流对象1、cout 标准输出流对象简介2、cout 常用 api 简介 二、cout.put(char c) 函数1、cout.put(char c) 函数 简介2、代码示例 - cout.put(char c) 函数 一、cout 标准输出流对象 1、cout 标准输出流对象简介 cout 是 标准输出流 对象 , 是 ostrea…

端口被占用 --- 解决方案

问题描述 加速服务启动失败&#xff0c;443端口被magentproc(1576)占用。请关掉占用443端口的程序或者尝试使用系统代理模式。 问题解决 按下 win R 打开 输入cmd 输入命令 netstat -ano | findstr 443 找到 0.0.0.0:443 对应的端口 (1576) 按下 ctrl shift esc, 打开任务管…

综述 2023-IEEE-TCBB:生物序列聚类方法比较

Wei, Ze-Gang, et al. "Comparison of methods for biological sequence clustering." IEEE/ACM Transactions on Computational Biology and Bioinformatics (2023). https://ieeexplore.ieee.org/document/10066180 被引次数&#xff1a;1&#xff1b;研究背景&am…

力扣题:数字与字符串间转换-12.13

力扣题-12.13 [力扣刷题攻略] Re&#xff1a;从零开始的力扣刷题生活 力扣题1&#xff1a;442. 数组中重复的数据 解题思想&#xff1a;直接相除即可 class Solution(object):def optimalDivision(self, nums):""":type nums: List[int]:rtype: str"&qu…

Transformer 简介

Transformer 是 Google 在 2017 年底发表的论文 Attention Is All You Need 中所提出的 seq2seq 模型。Transformer 模型的核心是 Self-Attention 机制&#xff0c;能够处理输入序列中的每个元素&#xff0c;并能计算其与序列中其他元素的交互关系的方法&#xff0c;从而能够更…

再见了Future,图解JDK21虚拟线程的结构化并发

Java为我们提供了许多启动线程和管理线程的方法。在本文中&#xff0c;我们将介绍一些在Java中进行并发编程的选项。我们将介绍结构化并发的概念&#xff0c;然后讨论Java 21中一组预览类——它使将任务拆分为子任务、收集结果并对其进行操作变得非常容易&#xff0c;而且不会不…

Unity中Shader黑白阀值后处理效果

文章目录 前言一、我们先来PS看一下黑白阀值的效果二、使用step(a,b)函数实现效果三、实现脚本控制黑白阀值1、在Shader属性面板定义控制阀值变量2、把step的a改为_Value3、在后处理脚本设置公共成员变量,并且设置范围为&#xff08;0&#xff0c;1&#xff09;4、在Graphics.B…

Cocos Creator:创建棋盘

Cocos Creator&#xff1a;创建棋盘 创建地图三部曲&#xff1a;1. 创建layout组件2. 创建预制体Prefab&#xff0c;做好精灵贴图&#xff1a;3. 创建脚本LayoutSprite.ts收尾工作&#xff1a; 创建地图三部曲&#xff1a; 1. 创建layout组件 使用layout进行布局&#xff0c;…

四十三、Redis基础

目录 一、认识NoSql 1、定义&#xff1a; 2、常见语法 3、与关系型数据库&#xff08;SQL&#xff09;的区别&#xff1a; 二、认识Redis 1、定义&#xff1a; 2、特征&#xff1a; 3、Key的结构&#xff1a; 三、安装Redis 四、Redis常见命令 1、数据结构介绍 2、…

关于DNS服务器地址总是127.0.0.1且无法解析域名地址

问题 笔者尝试nslookup解释域名时&#xff0c;出现服务器变成本地环回口地址&#xff0c;导致无法解析域名 C:\Users\Zsy>nslookup www.baidu.com 服务器: UnKnown Address: 127.0.0.1*** UnKnown 找不到 www.baidu.com: Server failed排查思路 尝试关闭虚拟网卡&#…

CSS的逻辑组合伪类

CSS 的逻辑组合伪类有 4 种&#xff0c;分别是&#xff1a;:not()、:is()、:where()和&#xff1a;has()。 否定伪类:not() :not 伪类选择器用来匹配不符合一组选择器的元素。由于它的作用是防止特定的元素被选中&#xff0c;它也被称为反选伪类&#xff08;negation pseudo-…

自动化测试框架 —— pytest框架入门篇

今天就给大家说一说pytest框架。 今天这篇文章呢&#xff0c;会从以下几个方面来介绍&#xff1a; 01、pytest框架介绍 pytest 是 python 的第三方单元测试框架&#xff0c;比自带 unittest 更简洁和高效&#xff0c;支持非常丰富的插件&#xff0c;同时兼容 unittest 框架。…

【C++】:AVL树

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关多态的知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数据结…