2023年11月26日发(作者:)
浅析C/C++程序运行过程中的动态存储管理
田绍敏
(华中师范大学 信息管理系 湖北 武汉 430079)
摘要:动态存储管理是计算机中作业在执行前不直接建立分区,而是在作业执行过程中建立
的。所建分区的大小可随作业或进程对内存的要求而不断改变。或者说是程序在其执行过程
中通过系统调用进行分区的分配或改变分区的大小。由于动态分区的大小是由作业需求量决
定的,故分区的长度预先并不固定,分区的个数也不断变动。因此这样做可以大大提高内存
的利用率,从而提高工作效率。本文将以C/C++语言为对象,对其在运行、实现、动态调整
策略等方面和内容做以简单的介绍。
关键词:C/C++语言;动态存储管理;内存分配
1. 引言
内存管理主要包括内存分配和回收、地址变换、内存扩充、内存共享和保护等
功能。数组的静态分配方法是需要对内存预先分配存储空间。在C/C++语言中。除了静
态分配外,还可以进行动态存储分配。即在程序运行阶段实现数据存储单元的分配。动
态分区的特点是动态创建分区;在调入程序时按其初始要求进行分配,或在其执行过程
中通过系统调用进行分配或改变分区大小。即由系统根据程序运行过程中实际需要对内
存进行实时的分配。而且分配的大小就是程序需要的大小。与静态分区相比,优点是:
没有内碎片。动态分区的分配就是程序装入时寻找一个大小大于或等于程序的要求的空
闲分区。该空闲区若是大于要求,则分割成两个区,其中一个分区被程序“占用”,而
另一余下的部分分区标记为“空闲”可以被其他进程调占用。分区分配从内存的低端到
高端的先后次进行。在各类操作系统中,当一个程序运行时,操作系统将为其分配一部
分存储区,供它保存代码和数据。其数据区里包括一块动态存储区,由这个程序的动态
存储管理系统管理,其运行过程中所需要的全部动态存储申请都在这块空间里进行分
配,程序的运行过程中如果有暂时不用的空间,动态存储管理系统就将其收回,这就是
释放。也就是把不用的存储块交还程序的动态存储管理系统。一旦这个程序结束,操作
系统就会收回它占用的所有存储空间,并与相邻的空闲分区合并成一个大的空闲分区。
这样对于运行较大程序大有好处。
2. 几种常用的分区分配算法
2.1 几种算法的简介
2.1.1 首次适应算法(first fit)
即按分区在内存的先后次序从头开始查找,找刭符合要求的第一个分区进行分配。
这个算法对内存的动态分配以及释放的时间性能较好。较大的空闲分区可以被保留在内
存高端。该算法的弊端是随着低端分区一次次的划分而产生的小分区数量的增加,造成
后续的分区因“碎块”太多而造成查找时间会不断增大。
2.1.2 循环首次适应算法(next fit)
此算法是从上次分配的分区起查找符合要求的第一个分区。该算法的分配和释放
的时间性能比较好,使空闲的分区分布得更加均匀,但是弊端是造成较大空闲分区保留
上不是很容易,也会造成太多“碎片”。
2.1.3 最佳适应算法(best fit)
该算法是在内存中按分区的先后次序从头开始查找,直到找到大小与所要求的分
区相差最小的空闲分区进行分配。从个别情况来看,这种算法造成的外碎片较小,但从
整体上来看,会形成较多的外碎片(外碎片:当已分配内存块之间出现未被使用的差额
时,就会产生外部碎片,即分区间难以得用的的空间)。但此种算法的优点是会有较大
的空闲分区可以被保留。
2.1.4 最坏适应算法(worst fit)
该算法是按分区在内存的先后次序从头查找,找到最大空闲分区进行分配,不易
形成外碎片。当有对内存需求较大的进程需要运行,其要求不易被满足。
2.2 几种算法的比较
特点 优点 缺点
首次适应简单、快1、 释放某一存储区时,若与空闲区相1、 低地址部分不断划分,
算法 速分配 邻则合并到相邻空闲分区中去,并形成很多难以利用的小
不改变该区在表中的位置,只要修块
改其大小或首址; 2、 增加了查找的时间
2、 尽可能地利用低地址空间,从而保
证高地址空间有较大的空闲区。
循环首次 减少查找空闲分区时的开销 缺乏大的空闲分区
适应算法
最佳适应用最小空1、 在系统中若存在一个与申请分区大1、 分割后的空闲区将会很
算法 间满足要小相等的空闲区,必定会被选中; 小,直至无法使用,而
求 2、 若系统中不存在与申请分区大小相造成浪费;
等的空闲区,则选中的空闲区是满2、 分配和回收后要对空闲
足要求的最小空闲区,而不致于毁区表(队列)重新排序。
掉较大的空闲区。
最坏适应当分配后1、 当程序装入内存中最大的空闲区
算法 剩下的空后,剩下的空闲区还可能相当大,
区仍可为还能装下较大的程序;
较大空区 2、 每次仅作一次查询工作。
3. C/C++语言中动态存储管理的实现
在C/C++语言中,动态存储管理是由一组标准的C语言的库函数来实现,其原型在
标准文件
存储分配有关的主要函数主要有以下四个:存储分配函数malloc()、带计数和清0的动
态存储分配函数calloc、动态存储释放函数free()、分配调整函数realloc。
3.1 存储分配函数malloc()
该函数的原型是:void *malloc(size N)。功能是从堆空间中分配大小为(size
是标准库里定义的一个类型,它是一个无符号整型)个字节的内存空间给本函数的调用
者。该函数不可以对分配的内存空间进行初始化。函数的返回值为void类型。(void是
C/C++语言数据类型。void是“空的”、“无效的”、“空虚”、“忽略”之意。void
体现了一种抽象,void真正发挥的作用在于:()对函数返回的限定;(2)对函数参数的
限定)。如果分配失败,则返回-个空指针(NULL)。分配失败的原因有多种,空间不足就
是其中一种。所以在调用该函数时应该检测返回值是否为NULL并执行相应的操作。调用
malloc时,应该利用sizeof计算存储块的大小,不要直接写整数,以避免不必要的错误。
3.2 带计数和清0的动态存储分配函数calloc
该函数的原型是:void *calloc(size_t,size_tsize)。功能是从堆空间中分配n
个大小为size个字节的内存空间。calloc将分配一块存储,其大小足以存放个大小各为
size的元素,分配之后会把存储块里全部清0(初始化为O值)。如分配成功,返回存储块
的首地址。如果分配不成功就返回NULL。例:程序片段里的存储分配也可以用下面语句
实现:
scores=(double*)calloc(n,sizeof(double));
3.3 动态存储释放函数free()
该函数的原型是:void free (void *p)。
此函数功能是释放指针p所指的内存区。使这部分内存能够被其它变量使用。free
函数无返回值。p是调用calloc或malloc函数时返回的值。如果当时的p值是空指针,free
就什么也不做。由于内存区域有限。每个程序都应尽量节省资源。当所分配的内存区域
不再使用时,就应及时将它释放,以便其它的变量或者程序使用。free函数此时可用。
例:
void Free(void *P,int n)
{while(Gettop(S,b)&& {Pop(S,b);Push(T,b); }//在按地址排序的栈中找到合适的插入位置 if(Gettop(T,b)&&(+==P))//可以与上邻块合并 {Pop(T,b); P=;n+=; }if(Gettop(S,b)&&(P+n==))//可以与下邻块合并 {Pop(S,b);n+=; }Push(S,(P,n));//插入到空闲块栈中 while(!StackEmpty(T)) {Pop(T,b);Push(S,b); }//恢复原来次序 //Free_void 3.4 分配调整函数realloc 该函数的原型是:void *realloc(void *p,unsigned n);功能:按size的大小, 修改由指针p指出的已分配内存块。此函数可以对给定的指针所指的空间进行扩大或者缩 小。无论是扩张或是缩小,原有内存的中内容将保持不变。当然,对于缩小,则被缩小 的那一部分的内容会丢失。该函数并不保证调整后的内存空间和原来的内存空间保持同 一内存地址。相反,realloc返回的指针很可能指向一个新的地址。所以在代码中,我们 必须将realloc返回的值,重新赋值给p,语法如下: p=(int *)realloc(p,sizeof(int)*15); 4. 在C/C++语言中使用动态存储管理的要点 (一) 必须检查分配的成功与否。首先在使用内存之前必须检查指针是否为NULL。如 果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。 (二)系统对动态分配块的使用不做检查。程序员需保证绝不可以超出实际存储块的 范围进行访问。因为越界访问可能造成严重错误。 (三)一个函数里分配的存储块的存在期与该函数的执行期无关。函数结束时不会自 动回收这一存储块,必须通过free释放(需由程序员通过写程序进行控制)。 (四)如果在函数里使用局部变量指向一个分配了的存储块,如何处理这个块就必须 在函数退出前考虑。如果这个块还有用,那么就应该把它的地址赋给存在期更长的变量; 如果这个块已经没用了,那么就应该通过free进行释放。 5. 函数、指针和动态存储 如果需要在函数里处理一组数据。并把处理结果返回到调用函数的地方,那么最佳 的方法就是在函数调用时提供数组的起始位置和结束位置。这样函数就无需知道调用的 是动态分配的存储块,还是程序里定义的数组变量。这样主函数main就可以完成位于同 一层次的分配和释放的责任。但并不是所有情况都能采用上述做法,如果我们在程序里 定义丁一个read scores(读入函数),程序需要根据输入情况确定如何申请动态存储, 这时被调用的read scores的内部就要存在动态存储的申请,此函数会完成向存储区里 填充数据的工作,最后把存储块的地址通过返回值送出来。read scores涵数是通过int 指针参数传递实际读入数据的个数。另一种做法是让函数返回一整数值。这样,我们在 main函数里就可以写如下形式的调用:if(readscores(„„)<=0)。如果这样设计函 数,调用的地方就需要通过实参取得函数里动态分配的存储块地址。同样。需要得到一 个指针值,就应该通过实参把这种指针变量的地址送进去,让函数通过该地址的调用对 指定的指针变量赋值。这样,修改后的函数readscorcs的原型应该是:int readscores(double**dpp)。 6.总结 以上简单的介绍了常用的分区分配算法以及C语言程序运行过程中指针、函数与动态 分配之间的一些关系。只要有可能,最好在程序的数据区里包含一块动态存储区,由这 个程序的动态存储管理系统进行管理,所有的动态存储申请都在这块存储区里进行分 配。这样做的好处是一旦程序结束,操作系统就会收回它占用的所有存储空问。这种方 法不易出现忘记释放的情况。如果在不得已的情况下采用了其它分配方法。一定要记得 存储管理过程中任务的交接问题,并在适当的时机和地方释放动态分配的存储区,以使 其他程序获得更多的动态存储区,以提高程序的运行效率及系统的高效性。 参考文献: [1]张代远. 计算机组成原理教程[M].清华大学出版社,2005 [2]陆志才. 微型计算机组成原理[M].高等教育出版社,2003 [3]成奋华 路慧民. C语言程序设计[M].科学出版社2006 [4]严蔚敏 吴伟民. 数据结构(C语言版)[M]清华大学出版社,2007 [5]汤小丹 哲凤屏.计算机操作系统[M].西安电子科技大学出版社,2007


发布评论