数据结构之动态内存管理机制(下)

简介: 数据结构之动态内存管理机制

数据结构之动态内存管理机制(中):https://developer.aliyun.com/article/1471377


在含有共享子表的广义表中,也可能会产生无用单元。例如图 1 中,L1、L2 和 L3 分别为三个广义表的表头指针,L4 为 L1 和 L2 所共享,L3 是 L2 的子表,L5 为 L1、L2 和 L3 三个广义表所共享。


在图 1 的基础上,假设表 L1 不再使用,而 L2 和 L3 还在使用,若释放表 L1,L1 中的所有结点所占的存储空间都会被释放掉,L2 和 L3 中由于同样包含 L1 中的结点,两个表会被破坏,某些指针会产生悬挂访问的错误;


而如果 L1 表使用完成后不及时释放,L1 中独自占用的结点由于未被释放,系统也不会回收,就会成为无用单元。


解决存储空间可能成为无用单元或者产生悬挂访问的方法有两个:


  1. 每个申请的存储空间设置一个计数域,这个计数域记录的是指向该存储空间的指针数目,只有当计数域的值为 0 时,该存储空间才会被释放。
  2. 在程序运行时,所有的存储空间无论是处于使用还是空闲的状态,一律不回收,当系统中的可利用空间表为空时,将程序中断,对当前不在使用状态的存储空间一律回收,全部链接成一个新的可利用空间表后,程序继续执行。


第一种方法非常简单,下面主要介绍第二种方法的具体实现。

第二种方法中,在程序运行过程中很难找出此时哪些存储空间是空闲的。解决这个问题的办法是:找当前正在被占用的存储空间,只需要从当前正在工作的指针变量出发依次遍历,就可以找到当前正在被占用的存储空间,剩余的自然就是此时处于空闲状态的存储空间。


如果想使用第二种方式,可以分为两步进行:


  • 对所有当前正在使用的存储空间加上被占用的标记(对于广义表来说,可以在每个结点结构的基础上,添加一个 mark 的标志域。在初始状态下,所有的存储空间全部标志为 0,被占用时标记为 1);
  • 依次遍历所有的存储空间,将所有标记为 0 的存储空间链接成一个新的可利用空间表。


对正在被占用的存储空间进行标记的方法有三种:

  • 从当前正在工作的指针变量开始,采用递归算法依次将所有表中的存储结点中的标志域全部设置为 1;
  • 第一种方法中使用递归算法实现的遍历。而递归底层使用的的存储结构,所以也可以直接使用栈的方式进行遍历;
  • 以上两种方法都是使用栈结构来记录遍历时指针所走的路径,便于在后期可以沿原路返回。所以第三种方式就是使用其他的方法代替栈的作用。


递归和非递归方式在前面章节做过详细介绍,第三种实现方式中代替栈的方法是:添加三个指针,p 指针指向当前遍历的结点,t 指针永远指向 p 的父结点,q 指向 p 结点的表头或者表尾结点。在遍历过程遵循以下原则:


当 q 指针指向 p 的表头结点时,可能出现 3 种情况:


  • p 结点的表头结点只是一个元素结点,没有表头或者表尾,这时只需要对该表头结点打上标记后即 q 指向 p 的表尾;
  • p 结点的表头结点是空表或者是已经做过标记的子表,这时直接令 q 指针指向 p 结点的表尾即可;
  • p 结点的表头是未添加标记的子表,这时就需要遍历子表,令 p 指向 q,q 指向 q 的表头结点。同时 t 指针相应地往下移动,但是在移动之前需要记录 t 指针的移动轨迹。记录的方法就是令 p 结点的 hp 域指向 t,同时设置 tag 值为 0。


当 q 指针指向 p 的表尾结点时,可能出现 2 种情况:


  • p 指针的表尾是未加标记的子表,就需要遍历该子表,和之前的类似,令 p 指针和 t 指针做相应的移动,在移动之前记录 t 指针的移动路径,方法是:用 p 结点的 tp 域指向 t 结点,然后在 t 指向 p,p 指向 q。
  • p 指针的表尾如果是空表或者已经做过标记的结点,这时 p 结点和 t 结点都回退到上一个位置。


由于 t 结点的回退路径分别记录在结点的 hp 域或者 tp 域中,在回退时需要根据 tag 的值来判断:如果 tag 值为 0 ,t 结点通过指向自身 hp 域的结点进行回退;反之,t 结点通过指向其 tp 域的结点进行回退。

image.png

图 2 待遍历的广义表


例如,图 2 中为一个待遍历的广义表,其中每个结点的结构如图 3 所示:

image.png

图 3 广义表中各结点的结构


在遍历如图 2 中的广义表时,从广义表的 a 结点开始,则 p 指针指向结点 a,同时 a 结点中 mark 域设置为 1,表示已经遍历过,t 指针为 nil,q 指针指向 a 结点的表头结点,初始状态如图 4 所示:

image.png

图 4 遍历广义表的初始状态


由于 q 指针指向的结点 b 的 tag 值为 1,表示该结点为表结构,所以此时 p 指向 q,q 指向结点 c,同时 t 指针下移,在 t 指针指向结点 a 之前,a 结点中的 hp 域指向 t,同时 a 结点中 tag 值设为 0。效果如图 5 所示:

image.png

图 5 遍历广义表(2)


通过 q 指针指向的结点 c 的 tag=1,判断该结点为表结点,同样 p 指针指向 c,q 指针指向 d,同时 t 指针继续下移,在 t 指针指向 结点 b 之前,b 结点的 tag 值更改为 0,同时 hp 域指向结点 a,效果图如图 6 所示:

image.png

图 6 遍历广义表(3)


通过 q 指针指向的结点 d 的 tag=0,所以,该结点为原子结点,此时根据遵循的原则,只需要将 q 指针指向的结点 d 的 mark 域标记为 1,然后让 q 指针直接指向 p 指针指向结点的表尾结点,效果图如图 7 所示:

image.png

图 7 遍历广义表(4)


当 q 指针指向 p 指针的表尾结点时,同时 q 指针为空,这种情况的下一步操作为 p 指针和 t 指针全部上移动,即 p 指针指向结点 b,同时 t 指针根据 b 结点的 hp 域回退到结点 a。同时由于结点 b 的tag 值为 0,证明之前遍历的是表头,所以还需要遍历 b 结点的表尾结点,同时将结点 b 的 tag 值改为 1。效果图如图 8 所示:

image.png

图 8 遍历广义表(5)


由于 q 指针指向的 e 结点为表结点,根据 q 指针指向的 e 结点是 p 指针指向的 b 结点的表尾结点,所以所做的操作为:p 指针和 t 指针在下移之前,令 p 指针指向的结点 b 的 tp 域指向结点 a,然后给 t 赋值 p,p 赋值 q。q 指向 q 的表头结点 f。效果如图 9 所示:

image.png

图 9 遍历广义表(6)


由于 q 指针指向的结点 f 为原子结点,所以直接 q 指针的 mark 域设为 1 后,直接令 q 指针指向 p 指针指向的 e 结点的表尾结点。效果如图 10 所示:

image.png

图 10 遍历广义表(7)


由于 p 指针指向的 e 结点的表尾结点为空,所以 p 指针和 t 指针都回退。由于 p 指针指向的结点 b 的 tag 值为 1,表明表尾已经遍历完成,所以 t 指针和 p 指针继续上移,最终遍历完成。


总结


无用单元的收集可以采用以上 3 中算法中任何一种。无论使用哪种算法,无用单元收集本身都是很费时间的,所以无用单元的收集不适用于实时处理的情况中使用。


内存紧缩(内存碎片化处理)


前边介绍的有关动态内存管理的方法,无论是边界标识法还是伙伴系统,但是以将空闲的存储空间链接成一个链表,即可利用空间表,对存储空间进行分配和回收。


本节介绍另外一种动态内存管理的方法,使用这种方式在整个内存管理过程中,不管哪个时间段,所有未被占用的空间都是地址连续的存储区。


这些地址连续的未被占用的存储区在编译程序中称为堆。

image.png

image.png

图 1 存储区状态


假设存储区的初始状态如图 1 所示,若采用本节介绍的方法管理这块存储区,当 B 占用块运行完成同时所占的存储空间释放后,存储区的状态应如图 2 所示:

image.png

图 2 更新后的存储区状态


分配内存空间


在分配内存空间时,每次都从可利用空间中选择最低(或者最高)的地址进行分配。具体的实现办法为:设置一个指针(称为堆指针),每次用户申请存储空间时,都是堆的最低(或者最高)地址进行分配。假设当用户申请 N 个单位的存储空间时,堆指针向高地址(或者低地址)移动 N 个存储单位,这 N 个存储单位即为分配给用户使用的空闲块,空闲块的起始地址为堆指针移动之前所在的地址。


例如,某一时间段有四个用户(A、B、C、D)分别申请 12 个单位、6 个单位、10 个单位和 8 个单位的存储空间,假设此时堆指针的初值为 0。则分配后存储空间的效果为:

image.png

回收算法


由于系统中的可利用空间始终都是一个连续的存储空间,所以回收时必须将用户释放的存储块合并到这个堆上才能够重新使用。


存储紧缩有两种做法:


  1. 其一是一旦用户释放所占空间就立即进行回收紧缩;
  2. 另外一种是在程序执行过程中不立即回收用户释放的存储块,而是等到可利用空间不够分配或者堆指针指向了可利用存储区的最高地址时才进行存储紧缩。


具体的实现过程是:

  1. 计算占用块的新地址。设立两个指针随巡查向前移动,分别用于指示占用块在紧缩之前和之后的原地址和新地址。因此,在每个占用块的第一个存储单位中,除了存储该占用块的大小和标志域之外,还需要新增一个新地址域,用于存储占用块在紧缩后应有的新地址,即建立一张新、旧地址的对照表。
  2. 修改用户的出事变量表,保证在进行存储紧缩后,用户还能找到自己的占用块。
  3. 检查每个占用块中存储的数据。如果有指向其它存储块的指针,则需作相应修改。
  4. 将所有占用块迁移到新地址去,即进行数据的传递。


最后,还要将堆指针赋以新的值。


总结


存储紧缩较之无用单元收集更为复杂,是一个系统的操作,如果不是非不得已不建议使用。


目录
相关文章
|
16小时前
|
Java C#
深入理解操作系统的内存管理机制
【4月更文挑战第25天】 在现代计算机系统中,操作系统扮演着关键的角色,它负责协调和管理硬件资源,确保系统运行的稳定性和效率。内存管理是操作系统中的核心职能之一,它涉及到物理内存的分配、虚拟内存的管理以及内存的优化等复杂过程。本文将深入探讨操作系统的内存管理机制,包括分页系统的实现原理、地址转换机制以及内存碎片问题的解决策略。通过对这些概念和技术的详细分析,读者将能够更好地理解操作系统如何高效地管理和利用计算机的内存资源。
|
16小时前
|
算法 程序员 调度
深入理解操作系统的内存管理机制
【5月更文挑战第9天】 在现代计算机系统中,操作系统的内存管理是一个至关重要的部分,它直接影响到系统的性能和稳定性。本文将深入探讨操作系统的内存管理机制,包括物理内存的管理、虚拟内存的概念和应用,以及内存分配和回收的策略。通过对这些内容的深入理解,我们可以更好地理解操作系统的工作原理,提高我们的编程效率和质量。
|
16小时前
|
算法
深入理解操作系统的内存管理机制
【4月更文挑战第30天】 在现代计算机系统中,操作系统扮演着至关重要的角色,它负责协调和管理硬件资源,确保系统高效、稳定地运行。其中,内存管理是操作系统的核心功能之一,涉及到物理内存的分配、虚拟内存的映射以及内存的优化等多个方面。本文将深入探讨操作系统中的内存管理机制,包括分页、分段和段页式结合等技术,旨在为读者提供一个清晰的内存管理框架视图,并解释这些技术如何提高系统的性能和稳定性。
|
16小时前
|
算法 Java 大数据
深入理解操作系统的内存管理机制
【4月更文挑战第27天】 在现代计算机系统中,操作系统扮演着至关重要的角色,尤其是内存管理作为其核心功能之一,它直接关系到系统性能和稳定性。本文将探讨操作系统中内存管理的基本原理、关键技术以及面临的挑战,旨在为读者提供一个清晰的内存管理概念框架,并通过分析不同内存管理策略的优缺点,揭示其对操作系统整体性能的影响。
|
16小时前
|
缓存 算法 调度
深入理解操作系统的内存管理机制
【4月更文挑战第27天】 在现代计算机系统中,操作系统扮演着至关重要的角色,尤其在资源管理和调度方面。内存管理是操作系统的核心功能之一,它负责分配、跟踪和回收应用程序使用的物理内存。本文将探讨操作系统如何通过不同的内存管理技术来优化内存使用效率,包括分页、分段以及虚拟内存等概念。通过对这些技术的深入分析,读者将获得对操作系统内部工作原理的更深刻理解,并了解它们如何影响应用程序性能和系统稳定性。
|
16小时前
|
缓存 算法 Java
深入理解操作系统的内存管理机制
【5月更文挑战第14天】 本文针对操作系统中至关重要的一环——内存管理机制进行深入剖析。不同于常规的资源整合和分配策略讨论,我们将聚焦于操作系统如何通过复杂的数据结构和算法优化内存使用效率,保证系统稳定性与性能。文章将详细探讨分页系统、虚拟内存以及内存碎片等问题的解决方案,并分析现代操作系统如何处理多核处理器下的内存共享与竞争条件。通过本文的阅读,读者将对操作系统的内存管理有一个全面而深刻的认识。
|
16小时前
|
存储 安全 Java
Python中的引用和赋值机制允许变量引用内存中的对象,并通过引用计数来管理对象的生命周期
【5月更文挑战第14天】Python中的变量是对象引用,不存储数据,而是在内存中创建对象。赋值操作创建新变量并使其指向已有对象。引用计数用于管理对象生命周期,引用数为0时对象被回收。理解这些机制对编写高效Python代码很重要。
14 6
|
16小时前
|
C++
数据结构(顺序表 动态定义
数据结构(顺序表 动态定义
12 2
|
16小时前
|
算法 安全 UED
深入理解操作系统的内存管理机制
【5月更文挑战第9天】 在本文中,我们将探讨操作系统的核心组件之一——内存管理。不同于传统的摘要概述,我们将直接切入主题,首先介绍内存管理的基础知识,然后深入讨论操作系统如何处理内存分配、内存保护以及虚拟内存技术。通过分析具体实例和案例研究,文章旨在为读者提供一个清晰的框架,以理解内存管理在现代操作系统中的实现和重要性。
6 0
|
16小时前
|
存储 内存技术
深入理解操作系统的内存管理机制
【5月更文挑战第9天】操作系统的内存管理机制是计算机科学中的核心概念,它负责协调和管理计算机的内存资源,确保系统的稳定性和效率。本文将深入探讨操作系统的内存管理机制,包括内存分配、内存保护和虚拟内存等关键技术,帮助读者更好地理解和掌握操作系统的运行原理。