对于c语言存储分配程序(malloc函数)实现的理解
内容主要出自《The C Programming Language》一书,不得不说这是一本程序员必读的书,我大二读了前面几章就扔到一边了,直到最近才又拿起来再读,找不到言语来形容我现在后悔的心情....读的时候发现书中有好几处写错的地方,可能是我的版本比较旧的原因吧,我在文章中都改了过来。
先贴上所有的代码然后再根据书中的解释细致的分析,建议最好把代码手敲一遍,理解会更深刻。
- typedef long Align;/*用于保证最受限类型可以对齐*/
- union header{ /*头部*/
- struct{
- union header *ptr; /*下一个空闲块的指针*/
- unsigned size; /*块的大小,空闲块被要求是头部长度的整倍数,size就是这个倍数,所以空闲块的实际长度是size*sizeof(header)*/
- }s;
- Align x;
- };
- typedef union header Header;
- static Header base; /*空的空闲块链表*/
- static Header *freep = NULL; /*指向空闲块链表的当前节点*/
- Header *morecore(unsigned); /*当malloc函数无法分配合适的存储空间时,morecore方法会向系统申请更多存储空间*/
- void free(void *);/*free方法用于释放malloc函数分配的内存空间,并将适当的空闲块合并成大块,减少碎片*/
- void *malloc(unsigned nbytes)
- {
- Header *p,*prevp;
- unsigned nunits;
- nunits = (nbytes+sizeof(Header)-)/sizeof(header)+;/*实际长度要比申请的大1,因为要考虑头部*/
- if((prevp = freep)== NULL){ /*还没有初始化空闲块*/
- base.s.ptr = freep = prevp = &base;
- base.s.size = ;
- }
- for(p = prevp->s.ptr;;prevp = p,p=p->s.ptr){
- if (p->s.size >= nunits) {/*该块足够大*/
- if (p->s.size == nunits) {/*大小正好*/
- prevp->s.ptr = p->s.ptr;
- }else{ /*当大于时,将尾部的足够大的块返回*/
- p->s.size -= nunits;
- p += p->s.size;
- p->s.size = nunits;
- }
- freep = prevp;
- return (void *)(p+);
- }
- if (p==freep) {
- if ((p = morecore(nunits)) == NULL) {
- return NULL;
- }
- }
- }
- }
- #define NALLOC 1024
- Header* morecore(unsigned nu)
- {
- char *cp,*sbrk(int);
- Header *up;
- if (nu<NALLOC) {
- nu = NALLOC;
- }
- cp = sbrk(nu*sizeof(Header));
- if (cp == (char*)-) {
- return NULL;
- }
- up = (Header*)cp;
- up->s.size = nu;
- free((void*)(up+));
- return freep;
- }
- void free(void *ap)
- {
- Header *bp,*p;
- bp = (Header*)ap -;/*bp是要被释放内存的头部的指针*/
- for (p = freep; !(bp>p&&bp<p->s.ptr); p = p->s.ptr) {/*从当前空闲块开始寻找bp是否位于p和p的下一个块空闲块之间*/
- if (p>=p->s.ptr&&(bp>p||bp<p->s.ptr)) {/*当p>=p->s.ptr时,p位于链表的尾部,p->s.ptr位于链表的头部*/
- break;
- }
- }
- if (bp+bp->s.size == p->s.ptr) {/*如果bp与后面的块相邻就合并了两个块*/
- bp->s.size += p->s.ptr->s.size;
- }else{
- bp->s.ptr = p->s.ptr;
- }
- if (p+p->s.size == bp) {/*如果bp与前面的空闲块相邻就合并两个块*/
- p->s.size += bp->s.size;
- p->s.ptr = bp->s.ptr;
- }else{
- p->s.ptr = bp;
- }
- freep = p;
- }
malloc函数管理着一些空闲存储空间,这些空间不一定是连续的,而是以空闲块链表的方式组织的,每个块包含一个长度、一个指向下一个空闲块的指针以及一个指向自身存储空间的指针。这些空闲块按照存储地址的升序组织,最后一块指向第一块。
当调用malloc函数申请空间时,malloc将扫描空闲块链表,直到找到一个足够大的块为止。该算法称为“首次适应”,与之相对的算法是“最佳适应”,它寻找满足条件的最小块。如果该块恰好与请求的大小相符合,则将它从链表中移走并返回给用户。如果该块太大,则将它分成两部分:大小合适的块返回给用户,剩下的部分留在空闲块链表中。如果找不到一个足够大的块,则向操作系统申请一个大块并加入到空闲块链表中。
释放过程也是首先搜索空闲块链表,以找到可以插入被释放块的合适位置。如果与被释放块相邻的一边是一个空闲块,则将这两个块合成一个更大的块,这样存储空间不会有太多的碎片。因为空闲块链表是以地址的递增顺序链接在一起的,所以很容易判断相邻的块是否空闲。malloc函数返回的存储空间要满足将要保存的对象的对齐要求。虽然及其类型各异,但是,每个特定的机器都一个最受限的类型:如果最受限的类型可以存储在某个特定的地址中,则其它所有的类型也可以存放在此地址中。在某些机器中,最受限的类型是double类型;而在另外一些机器中,最受限的类型是int或long类型。
位于块开始处的控制信息被称为“头部”,为了简化块的对齐,所有块的大小都必须是头部大小的整数倍,且头部已正确地对齐。这是通过一个联合实现的,该联合包含所需的头部结构以及一个对齐要求最受限的类型的实例,在下面这段程序中,我们假定long类型为最受限的类型:
- typedef long Align;/*用于保证最受限类型可以对齐*/
- union header{ /*头部*/
- struct{
- union header *ptr; /*下一个空闲块的指针*/
- unsigned size; /*块的大小,空闲块被要求是头部长度的整倍数,size就是这个倍数,所以空闲块的实际长度是size*sizeof(header)*/
- }s;
- Align x;
- };
- typedef union header Header;
在该联合中,Align字段永远不会被使用,它仅仅用于强制每个头部在最坏的情况下满足对齐要求。在malloc函数中,请求的长度(以字符为单位)将被舍入,以保证它是头部大小的整数倍。实际分配的块将多包含一个单元,用于头部本身。实际分配的块的大小将被记录在头部的size字段中。malloc函数返回的指针将指向空闲空间,而不是头部的块。用户可对获得的存储空间进行任何操作,但是,如果在分配的存储空间之外写入数据,则可能会破坏块链表。
其中的size字段是必须的,因为由malloc函数控制的块不一定是连续的,这样就不可能通过指针算术运算计算其大小。
- static Header base; /*空的空闲块链表*/
- static Header *freep = NULL; /*指向空闲块链表的当前节点*/
- Header *morecore(unsigned); /*当malloc函数无法分配合适的存储空间时,morecore方法会向系统申请更多存储空间*/
- void free(void *);/*free方法用于释放malloc函数分配的内存空间,并将适当的空闲块合并成大块,减少碎片*/
- void *malloc(unsigned nbytes)
- {
- Header *p,*prevp;
- unsigned nunits;
- nunits = (nbytes+sizeof(Header)-)/sizeof(header)+;/*实际长度要比申请的大1,因为要考虑头部*/
- if((prevp = freep)== NULL){ /*还没有初始化空闲块*/
- base.s.ptr = freep = prevp = &base;
- base.s.size = ;
- }
- for(p = prevp->s.ptr;;prevp = p,p=p->s.ptr){
- if (p->s.size >= nunits) {/*该块足够大*/
- if (p->s.size == nunits) {/*大小正好*/
- prevp->s.ptr = p->s.ptr;
- }else{ /*当大于时,将尾部的足够大的块返回*/
- p->s.size -= nunits;
- p += p->s.size;
- p->s.size = nunits;
- }
- freep = prevp;
- return (void *)(p+);
- }
- if (p==freep) {
- if ((p = morecore(nunits)) == NULL) {
- return NULL;
- }
- }
- }
- }
base表示空闲块链表的头部。第一次调用malloc函数时,freep为NULL,系统将创建一个退化的空闲块链表,它只包含一个大小为0的块,且该块指向它自己。任何情况下,当请求空闲空间时,都将搜索空闲块链表。搜索从上一次找到空闲块的地方(freep)开始。该策略可以保证链表是均匀的。如果找到的块太大,则将其尾部返回给用户,这样,初始块的头部只需要修改size字段即可。在任何情况下,返回给用户的指针都指向块内的空闲存储空间,即比指向头部的指针大一个单元。
- #define NALLOC 1024
- Header* morecore(unsigned nu)
- {
- char *cp,*sbrk(int);
- Header *up;
- if (nu<NALLOC) {
- nu = NALLOC;
- }
- cp = sbrk(nu*sizeof(Header));
- if (cp == (char*)-) {
- return NULL;
- }
- up = (Header*)cp;
- up->s.size = nu;
- free((void*)(up+));
- return freep;
- }
函数morecore用于向操作系统请求存储空间,其实现细节因系统的不同而不同。因为向系统请求存储空间是一个开销很大的操作,因此,我们不希望每次调用malloc函数时都执行该操作,基于这个考虑,morecore函数请求至少NALLOC个单元。这个较大的块将根据需要分成较小的块。在设置完size字段之后,morecore函数调用free函数把多余的存储空间插入到空闲区域中。UNIX系统调用sbrk(n)返回一个指针,该指针指向n个字节的存储空间。如果没有空闲空间,尽管返回NULL可能更好一些,但sbrk调用返回-1.必须将-1强制转换为char*类型,以便与返回值进行比较。而且,强制类型转换使得该函数不会受不同机器中指针表示的不同的影响。但是,这里仍然假定,由sbrk调用返回的指向不同块的多个指针之间可以进行有意义的比较。ANSI标准并没有保证这一点,它只允许指向同一个数组的指针间的比较。因此,只有在一般指针间的比较操作有意义的机器上,该版本的malloc函数才能够移植。
- void free(void *ap)
- {
- Header *bp,*p;
- bp = (Header*)ap -;/*bp是要被释放内存的头部的指针*/
- for (p = freep; !(bp>p&&bp<p->s.ptr); p = p->s.ptr) {/*从当前空闲块开始寻找bp是否位于p和p的下一个块空闲块之间*/
- if (p>=p->s.ptr&&(bp>p||bp<p->s.ptr)) {/*当p>=p->s.ptr时,p位于链表的尾部,p->s.ptr位于链表的头部*/
- break;
- }
- }
- if (bp+bp->s.size == p->s.ptr) {/*如果bp与后面的块相邻就合并了两个块*/
- bp->s.size += p->s.ptr->s.size;
- }else{
- bp->s.ptr = p->s.ptr;
- }
- if (p+p->s.size == bp) {/*如果bp与前面的空闲块相邻就合并两个块*/
- p->s.size += bp->s.size;
- p->s.ptr = bp->s.ptr;
- }else{
- p->s.ptr = bp;
- }
- freep = p;
- }
我们最后来看一下free函数。它从freep指向的地址开始,逐个扫描空闲块链表,寻找可以插入空闲块的地方。该位置可能在两个空闲块之间,也可能在链表的末尾。在任何一种情况下,如果被释放的块与另一空闲块相邻,则将这两个块合并起来。合并两个块的操作很简单,只需要设置指针指向正确的位置,并设置正确的块大小就可以了。虽然存储分配从本质上是与机器相关的,但是,以上的代码说明了如何控制与具体机器相关的部分,并将这部分程序控制到最少量。typedef和union的使用解决了地址的对齐(假定sbrk返回的是合适的指针)问题。类型的强制转换使得指针的转换是显式进行的,这样做甚至可以处理设计不够好的系统接口问题。虽然这里所讲的内容只涉及到存储分配,但是,这种通用方法也适用于其它情况。
对于c语言存储分配程序(malloc函数)实现的理解的更多相关文章
- C语言中的程序终止函数
在C语言的标准库<stdlib.h>中提供了一些与正常或者不正常的程序终止有关的函数,下面分别对其进行简单介绍. 参考文献: [1] C和指针,P298,342 [2] C程序设计语言现代 ...
- malloc函数详解 C语言逻辑运算符
今天写线性表的实现,又遇到了很多的难题,C语言的指针真的没学扎实.很多基础都忘了. 一是 :malloc 函数的使用. 二是:C语言逻辑运算符. 一.原型:extern void *malloc(un ...
- malloc函数的一种简单的原理性实现
malloc()是C语言中动态存储管理的一组标准库函数之一.其作用是在内存的动态存储区中分配一个长度为size的连续空间.其参数是一个无符号整形数,返回值是一个指向所分配的连续存储域的起始地址的指针 ...
- C语言第十一次作业--函数嵌套调用
一.实验作业 1.1 PTA题目:递归法对任意10个数据按降序排序 设计思路 定义整型循环变量i,最小值下标min,中间变量t 若n==1,直接返回 否则 min=10-n 最小值下标赋初值 for ...
- malloc 函数工作机制(转)
malloc()工作机制 malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表.调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块.然后,将 ...
- C语言malloc函数为一维,二维,三维数组分配空间
c语言允许建立内存动态分配区域,以存放一些临时用的数据,这些数据不必在程序的声明部分定义,也不必等到函数结束时才释放,而是需要时随时开辟,不需要时随时释放,这些数据存储在堆区.可以根据需要,向系统申请 ...
- C语言动态存储分配
动态存储分配 C语言支持动态存储分配,即在程序执行期间分配内存单元的能力,利用动态存储分配,可以根据需要设计扩大(或缩小)的数据结构,虽然可以适用于所有类型的数据,但是动态存储分配更常用于字符串.数组 ...
- 关于c语言内存分配,malloc,free,和段错误,内存泄露
1. C语言的函数malloc和free (1) 函数malloc和free在头文件<stdlib.h>中的原型及参数 void * malloc(size_t size ...
- C语言malloc()函数:动态分配内存空间
头文件:#include <stdlib.h> malloc() 函数用来动态地分配内存空间(如果你不了解动态内存分配,请查看:C语言动态内存分配及变量存储类别),其原型为:void* m ...
随机推荐
- (5)UIView常见属性
此时打印的所有子控件会把使用自动布局的控件也打印出来,不准确,所以得去掉这两个选项,再进行打印 使用实例如下: viewWithTag的注意点,当有多个相同的Tag值时,它是先找到第一个Tag值,而不 ...
- Tomcat结构、启动过程、关键组件简单分析
Tomcat 结构: Tomcat最顶层容器叫Server,代表整个服务器,Server中包含至少一个Service,用于具体提供服务,Serv ...
- Linux Command Line(II): Intermediate
Prerequisite: Linux Command Line(I): Beginner ================================ File I/O $ cat > a ...
- Scala关于软件的安装(《Programming in Scala》这本书)
这篇文章主要介绍怎么在终端来写Scala语言.在Scala创始人出的Coursera课程中是让我们在IDE中sbt写Scala语言,而在这本书貌似让我们在终端上直接用Scala写.官网有介绍不过不详细 ...
- Learn c for the Second day
十六进制对应的二进制码 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 0 ...
- [JLOI2013]删除物品 树状数组
当时考试时间剩下太短了然后就挂掉了..其实是个简单的数据结构. 话说一看最小还以为是动规呢.. 将两堆头对头排.比如样例就是 541|273 因为是必须有优先级次序,依次拿的话,看优先级大小相邻的两个 ...
- latch相关视图整理
latch相关视图整理(原创) V$LATCH V$LATCH视图在选取X$KSLLT记录时,进行了Group By及SUM运算,从而得出了一个汇总信息,保存了自实例启动后各类栓锁的统计信息.常用于当 ...
- MyBatis实现Mysql数据库分库分表操作和总结
前言 作为一个数据库,作为数据库中的一张表,随着用户的增多随着时间的推移,总有一天,数据量会大到一个难以处理的地步.这时仅仅一张表的数据就已经超过了千万,无论是查询还是修改,对于它的操作都会很耗时,这 ...
- CentOS 7 服务器配置--安装CentOS 7
看博客园也有几年的时间了,这是第一次鼓足勇气发一下我自己的博客,不为了别的,只是最近打算学习Linux的服务器配置,就写几篇博文把我学的一点点的记录下来,方便以后自己回顾学习. 今天就先记录下我安装C ...
- Vue动态组件
前面的话 让多个组件使用同一个挂载点,并动态切换,这就是动态组件.本文将详细介绍Vue动态组件 概述 通过使用保留的 <component> 元素,动态地绑定到它的 is 特性,可以实现动 ...