| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 1978 人关注过本帖, 1 人收藏
标题:小鱼儿の模拟内存动态分配学习
只看楼主 加入收藏
小鱼儿c
Rank: 9Rank: 9Rank: 9
等 级:贵宾
威 望:14
帖 子:852
专家分:1317
注 册:2011-4-1
结帖率:95.74%
收藏(1)
已结贴  问题点数:100 回复次数:17 
小鱼儿の模拟内存动态分配学习
无意间看到c语言进阶看到了。
于是研究了一下。。
这方面新手都很少接触,所以我觉的有必要学一下。。。
下面我写的。。。。。
my_malloc.h

程序代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//模拟内存大小
#define memoBuf   640
//最小的块差,是为了减少内存碎片。
#define mini   8

typedef struct mem_control_info
{
    unsigned int size;//空闲块大小
    struct mem_control_info *next;//指向下一个空闲块
}mem_info,*pmem_info;



//Base=(struct mem_control_info *)malloc(memoBuf);

void mem_init();
void * _malloc(int bytes);
void _free(void *p);
void _print();
void _print_list();
static int merge(pmem_info p,pmem_info pre,pmem_info next);


my_malloc.cpp

程序代码:
#include "my_malloc.h"
/************************************************************************/
/*                                内存动态管理管理
/*  1:内存算法:首次分配内存
/*  2: 优化:如果空闲块大与所需,就把空闲块的高地址分配给用户,这样写算法更方便。也减少了内存碎片。
/*  3:合并相邻空闲内存块。这样减少内存碎片。 
/*  4:分配内存的时候,把指针指向自己 用于释放的时候 检测是否为合法指针。
/*  5:因为分配内存也和这样类似,不然指针不合法会造成空闲块的链表混乱,所以写检验是很重要的。                                                                    
/************************************************************************/

/************************************************************************/
/*申明程序还在测试中,自己也没怎么写好。
/*我没有用字节分配,没有转换为内存块 貌似把字节为2幂 这个我也不知道为什么
/*这个程序我看了好多的,现在也只有写这么简单.发现BUG希望提出来。
/*写的不好 请告诉哪里。我将改进。我把程序写更加明了,我是学习我提在论坛的问题
/*写的  我写的更适合新手思维。。                                                                      
/************************************************************************/

char memosize[memoBuf];
int mem_size=sizeof(mem_info);//空闲块大小

pmem_info First;//First 是内存块的起始地址。它是一个特殊空闲块

int has_init=0;
void mem_init()
{
    First=(pmem_info)memosize;
    First->size=memoBuf-mem_size;
    First->next=NULL;
}
void _print()
{
    printf("可分配的内存%d",First->size);
    getchar();
}
void _print_list()
{
    pmem_info p;
    for(p=First;p;p=p->next)
    {
        printf("%d\t",p);
        printf("size is %d \n",p->size);
    }
}
static int merge(pmem_info p,pmem_info pre,pmem_info next)
{
    //printf("will free  is %d , pre is %d pre size %d",p,pre,pre->size);
    //puts("\n");
    //printf("%d",(char *)pre+pre->size);
    if((char *)p==(char *)pre+pre->size)//用char *的指针 因为是字节数为1.
    {
        pre->size+=p->size;
        return 1;
    }
    else
        if(p+p->size==next)
        {
            p->size+=next->size;
            pre->next=p;
            return 1;
        }
        return 0;
}

void *_malloc(int bytes)
{
    char *p;
    int nb=bytes+mem_size;
    pmem_info currPtr,pre;
    if(bytes<=0)
        return NULL;
    pre=NULL;
    nb=bytes+mem_size;
    if(!has_init)
    {
        mem_init();
    }
    for(currPtr=First;currPtr;pre=currPtr,currPtr=currPtr->next)
    {
         if(currPtr->size-nb>=0)
         {
             if(currPtr->size-nb<mini)
             {
                 if(pre==NULL)
                 {
                     First->size=0;
                 }
                 else
                 {
                     pre->next=currPtr->next;
                 }

             }
             else 
             {
                 currPtr->size-=nb;
                 p=(char*)currPtr;
                 p=p+currPtr->size;
                 currPtr=(pmem_info)p;
                 currPtr->size=nb;
             }
             currPtr->next=currPtr;
             return (void *)(currPtr+1);     
         }
         
    }
    return NULL;
}

void _free(void *p)
{
  pmem_info temp;
  int test;
  pmem_info currPtr;
  temp=(pmem_info)p;
  temp--;
  if(temp!=temp->next)
  return;
  for(currPtr=First;currPtr;currPtr=currPtr->next)
  {
      if(temp>currPtr&&temp<currPtr->next)
      {
            if(!(test=merge(temp,currPtr,currPtr->next)))
            {
                temp->next=currPtr->next;
                currPtr->next=temp;
            }
            
      }
      else
          if(temp>currPtr&&currPtr->next==NULL)
          {
              if(!(test=merge(temp,currPtr,currPtr->next)))
              {
                  currPtr->next=temp;
                  temp->next=NULL;
              }
          }
  }
}

main.cpp
程序代码:
#include "my_malloc.h"

//这个结构用来测试用的。。
struct tagpp
{
    int x;
    int y;
};
int main()
{
    struct tagpp *xy;
    puts("test");
    xy=(struct tagpp*)_malloc(10);
    if(!xy)
    {
        puts("内存分配失败");
        _print();
    }

    xy->x=1;
    xy->y=2;
    printf("%d  %d\n",xy->x,xy->y);
    _malloc(100);
    _malloc(100);
    _malloc(100);
    _malloc(100);
    _print_list();
    _free(xy);
    _malloc(4);
    _print_list();
    return 0;
}


我找的一些有关的资料


// 转载。还没看明白,有待修改。
malloc()是C语言中动态存储管理的一组标准库函数之一。其作用是在内存的动态存储区中分配一个长度为size的连续空间。其参数是一个无符号整形数,返回值是一个指向所分配的连续存储域的起始地址的指针
malloc()工作机制
malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。
malloc()在操作系统中的实现
在 C 程序中,多次使用malloc () 和 free()。不过,您可能没有用一些时间去思考它们在您的操作系统中是如何实现的。本节将向您展示 malloc 和 free 的一个最简化实现的代码,来帮助说明管理内存时都涉及到了哪些事情。
在大部分操作系统中,内存分配由以下两个简单的函数来处理:
void *malloc (long numbytes):该函数负责分配 numbytes 大小的内存,并返回指向第一个字节的指针。
void free(void *firstbyte):如果给定一个由先前的 malloc 返回的指针,那么该函数会将分配的空间归还给进程的“空闲空间”。
malloc_init 将是初始化内存分配程序的函数。它要完成以下三件事:将分配程序标识为已经初始化,找到系统中最后一个有效内存地址,然后建立起指向我们管理的内存的指针。这三个变量都是全局变量:
  
        //清单 1. 我们的简单分配程序的全局变量
        int has_initialized = 0;
        void *managed_memory_start;
        void *last_valid_address;
如前所述,被映射的内存的边界(最后一个有效地址)常被称为系统中断点或者 当前中断点。在很多 UNIX? 系统中,为了指出当前系统中断点,必须使用 sbrk(0) 函数。 sbrk 根据参数中给出的字节数移动当前系统中断点,然后返回新的系统中断点。使用参数 0 只是返回当前中断点。这里是我们的 malloc 初始化代码,它将找到当前中断点并初始化我们的变量:
  
清单 2. 分配程序初始化函数
/* Include the sbrk function */
 
#include
void malloc_init()
{
/* grab the last valid address from the OS */
last_valid_address = sbrk(0);
/* we don''t have any memory to manage yet, so
 *just set the beginning to be last_valid_address
 */
managed_memory_start = last_valid_address;
/* Okay, we''re initialized and ready to go */
 has_initialized = 1;
}
现在,为了完全地管理内存,我们需要能够追踪要分配和回收哪些内存。在对内存块进行了 free 调用之后,我们需要做的是诸如将它们标记为未被使用的等事情,并且,在调用 malloc 时,我们要能够定位未被使用的内存块。因此, malloc 返回的每块内存的起始处首先要有这个结构:
  
//清单 3. 内存控制块结构定义
struct mem_control_block {
    int is_available;
    int size;
};
现在,您可能会认为当程序调用 malloc 时这会引发问题 —— 它们如何知道这个结构?答案是它们不必知道;在返回指针之前,我们会将其移动到这个结构之后,把它隐藏起来。这使得返回的指针指向没有用于任何其他用途的内存。那样,从调用程序的角度来看,它们所得到的全部是空闲的、开放的内存。然后,当通过 free() 将该指针传递回来时,我们只需要倒退几个内存字节就可以再次找到这个结构。
在讨论分配内存之前,我们将先讨论释放,因为它更简单。为了释放内存,我们必须要做的惟一一件事情就是,获得我们给出的指针,回退 sizeof(struct mem_control_block) 个字节,并将其标记为可用的。这里是对应的代码:
  
清单 4. 解除分配函数
void free(void *firstbyte) {
    struct mem_control_block *mcb;
/* Backup from the given pointer to find the
 * mem_control_block
 */
   mcb = firstbyte - sizeof(struct mem_control_block);
/* Mark the block as being available */
  mcb->is_available = 1;
/* That''s It!  We''re done. */
  return;
}
如您所见,在这个分配程序中,内存的释放使用了一个非常简单的机制,在固定时间内完成内存释放。分配内存稍微困难一些。我们主要使用连接的指针遍历内存来寻找开放的内存块。这里是代码:
  
//清单 6. 主分配程序
void *malloc(long numbytes) {
    /* Holds where we are looking in memory */
    void *current_location;
    /* This is the same as current_location, but cast to a
    * memory_control_block
    */
    struct mem_control_block *current_location_mcb;
    /* This is the memory location we will return.  It will
    * be set to 0 until we find something suitable
    */
    void *memory_location;
    /* Initialize if we haven''t already done so */
    if(! has_initialized) {
        malloc_init();
    }
    /* The memory we search for has to include the memory
    * control block, but the users of malloc don''t need
    * to know this, so we''ll just add it in for them.
    */
    numbytes = numbytes + sizeof(struct mem_control_block);
    /* Set memory_location to 0 until we find a suitable
    * location
    */
    memory_location = 0;
    /* Begin searching at the start of managed memory */
    current_location = managed_memory_start;
    /* Keep going until we have searched all allocated space */
    while(current_location != last_valid_address)
    {
    /* current_location and current_location_mcb point
    * to the same address.  However, current_location_mcb
    * is of the correct type, so we can use it as a struct.
    * current_location is a void pointer so we can use it
    * to calculate addresses.
        */
        current_location_mcb =
            (struct mem_control_block *)current_location;
        if(current_location_mcb->is_available)
        {
            if(current_location_mcb->size >= numbytes)
            {
            /* Woohoo!  We''ve found an open,
            * appropriately-size location.
                */
                /* It is no longer available */
                current_location_mcb->is_available = 0;
                /* We own it */
                memory_location = current_location;
                /* Leave the loop */
                break;
            }
        }
        /* If we made it here, it''s because the Current memory
        * block not suitable; move to the next one
        */
        current_location = current_location +
            current_location_mcb->size;
    }
    /* If we still don''t have a valid location, we''ll
    * have to ask the operating system for more memory
    */
    if(! memory_location)
    {
        /* Move the program break numbytes further */
        sbrk(numbytes);
        /* The new memory will be where the last valid
        * address left off
        */
   
        memory_location = last_valid_address;
        /* We''ll move the last valid address forward
        * numbytes
        */
        last_valid_address = last_valid_address + numbytes;
        /* We need to initialize the mem_control_block */
        current_location_mcb = memory_location;
        current_location_mcb->is_available = 0;
        current_location_mcb->size = numbytes;
    }
    /* Now, no matter what (well, except for error conditions),
    * memory_location has the address of the memory, including
    * the mem_control_block
    */
    /* Move the pointer past the mem_control_block */
    memory_location = memory_location + sizeof(struct mem_control_block);
    /* Return the pointer */
    return memory_location;
 }
这就是我们的内存管理器。现在,我们只需要构建它,并在程序中使用它即可.多次调用malloc()后空闲内存被切成很多的小内存片段,这就使得用户在申请内存使用时,由于找不到足够大的内存空间,malloc()需要进行内存整理,使得函数的性能越来越低。聪明的程序员通过总是分配大小为2的幂的内存块,而最大限度地降低潜在的malloc性能丧失。也就是说,所分配的内存块大小为4字节、8字节、16字节、18446744073709551616字节,等等。这样做最大限度地减少了进入空闲链的怪异片段(各种尺寸的小片段都有)的数量。尽管看起来这好像浪费了空间,但也容易看出浪费的空间永远不会超过50%。

[local]1[/local]
搜索更多相关主题的帖子: 学习 c语言 
2011-09-21 23:07
czsbc
Rank: 10Rank: 10Rank: 10
等 级:青峰侠
帖 子:469
专家分:1700
注 册:2008-12-13
收藏
得分:8 
顶一下,好长。
2011-09-21 23:12
BlueGuy
Rank: 16Rank: 16Rank: 16Rank: 16
等 级:版主
威 望:29
帖 子:4476
专家分:4055
注 册:2009-4-18
收藏
得分:8 
学习,学习...

[ 本帖最后由 BlueGuy 于 2011-9-21 23:20 编辑 ]
收到的鲜花
  • 小鱼儿c2011-09-21 23:27 送鲜花  2朵   附言:版主,见笑了。。。。 貌似简单检测了一下 ...

我就是真命天子,顺我者生,逆我者死!
2011-09-21 23:14
小鱼儿c
Rank: 9Rank: 9Rank: 9
等 级:贵宾
威 望:14
帖 子:852
专家分:1317
注 册:2011-4-1
收藏
得分:0 
malloc/free模拟

前言
看《The C Programming_Language》时,对8.7章的malloc讲解感觉不是很理解。趁着时间空闲,在内网里憋了几天,顺便将一些心得和资料记录如下,也让荒废多日的blog添点新东西^_^。
该文中一些注意地方如下,大体思路按照书中原码,改动的部分在“代码中的讲解”这段中有说明;本人在学习过程中难免有一些疏漏,知识水平有限,错误的地方还请大家斧正。
在附件中已经包含可运行代码,一些注意和编译环境都在源代码中说明,可随意转载,请保留版权说明即可。

因为文章比较长,一些段落小题目整理如下:
模拟概念图
申明内存块的规则
释放内存块的规则
内存块结构的定义
分配空间单位大小的设置
代码中的讲解
这里只有9可以满足相邻的条件?
程序运行结果展示

模拟概念图
在本文中将会完成以下功能,先malloc出一大段内存,模拟系统中可用的malloc控制的那段连续内存,然后对它进行申明调用和释放。或者说这也是一个小型的内存池模拟。在书中对malloc内存排列规则有如下的描述:
Since other activities in the program may also request space without calling this allocator, the space that malloc manages may not be contiguous. Thus its free storage is kept as a list of free blocks. Each block contains a size, a pointer to the next block, and the space itself. The blocks are kept in order of increasing storage address, and the last block (highest address) points to the first.

一张完整的模拟图如下,图中红色的数字标志为了方便一些描述而加上
 

为了更好的理解英文和图,我们这里设想一下现场情况。
假设一个QQ程序启动了,那么他使用的部分内存可能不需要malloc,可以是静态的,自然的,0就归它所有了。可能就这么巧合,系统中给某个程序堆的内存和0号是相邻的,也就是malloc的控制,这中间可能进行了一些优化调整,比如分成10MB的若干块,1MB的若干块等。(就成了1-6的链表块)。接着你启用了你的demo1程序,它得需要10M的内存(in use 1);你又启动了你的demo2程序,它需要20MB的内存,很不幸,2和3都小于这个容量,还好in use 4满足了这个要求(尽管从图中看起来3似乎比4大块点)。这样若干后,另一个类似QQ的程序启动了,他也需要一些静态,因而7就被它所占领了,再或者需要更太的内存块,10就这么来了。。。。
当然这只是很简单的描述,就比如6和8之间的7可能是经过一系列复杂的变化后掺杂进去,我这么描述只是简单的描绘内存的使用之所以会成如上图的原因。。

摆在我们面前的几个问题如下:一些定义就引用书中的话
申明内存块的规则
When a request is made, the free list is scanned until a big-enough block is found. This algorithm is called ``first fit,'' by contrast with ``best fit,'' which looks for the smallest block that will satisfy the request. If the block is exactly the size requested it is unlinked from the list and returned to the user. If the block is too big, it is split, and the proper amount is returned to the user while the residue remains on the free list. If no big-enough block is found, another large chunk is obtained by the operating system and linked into the free list.
文中只使用"first fit",如果太大则进行分割,将割好的内存返回给用户。如果链中没有合适的内存则向系统申请。该程序中为了简单模拟没有进行这一步,但是通过一些申请、释放也形成了一个不连续的链表,会在后边的块配合代码详细描述。

释放内存块的规则
Freeing also causes a search of the free list, to find the proper place to insert the block being freed. If the block being freed is adjacent to a free block on either side, it is coalesced with it into a single bigger block, so storage does not become too fragmented. Determining the
adjacency is easy because the free list is maintained in order of decreasing address.
简单的说就是根据地址的排列的一些特性,比如从低到高(该程序中的new只是在X86的WINDOWS机上试过是从低到高的连续地址),放在顺序的地址里并入,这里又分在头部和尾部,连续和不连续等几种。会在后边的块配合代码详细描述。

内存块结构的定义
首先是结构头部的定义,必然包括了一个指向下几一个结点的指针,一个描述该内存块大小的变量,如下所示
C++代码  
struct header  
{  
    header *ptr;  
    unsigned int size;  
};  

sizeof(header) = 8;而我们分配内存是以字节sizeof(int)为单位,这里要注意下计算。原书中是以header和分配的单位量大小一致,这里略有改动。

那么在内存中如下表示,初始大小
Java代码  
ptr   | size |   size表示的内存大小                                     |  
____________________________________________________________________________________  
0     3      7                                                                     n  


我们来试试,当申请一个 size1大小的内存(尾部返回),(0<size1<size),需要做如下操作
1、从header.size减去size1大小
2、在size内存段里产生一个对size1描述的header结构
3、将size1内存返回,
虚拟图变成如下
Java代码  
ptr   | size |   size-size1-1         | ptr  | size1|       size1-1      |  
____________________________________________________________________________________  
0     3      7                        |header size=8|                              n  


那么其实申请的是header+size1的大小,ptr = size-size1-1;

为了方便释放的描述,假设申请了size1,size2,那就继续从(size-size1-1)这段内存里分割出size2的大小
Java代码  
ptr   | size | size-size1-1-size2-1   | ptr|size2| size2-1    |ptr|size1|size1-1                |  
_________________________________________________________________________________________________  
0     3      7                                                              n  




分配空间单位大小的设置
这里为了CPU一个周期的访问,单位的长度为CPU位数/8,比如一台32位的单位长度就是4个字节,不过一些编译器也
可能把他设成8个字节(vs就是8个字节,gcc是4个字节)。那么同理,header也必须是如上的规则.
C++代码  
struct header  
{  
    header *ptr;              
    unsigned int size;  
}  

其中指针的大小跟CPU位数相关,为CPU位数/8,unsigned int size;表示的范围是最大能申请的内存,约4G左右(没人会申请那么多吧),变量大小也是4个字节,符合对齐的规律。那么可知一个块的最小值是sizeof(header)+CPU位数/8,这里还需要一个公式,来对传入的申请值进行计算,以达到以上的对齐规律。假设传入为n,那么将其变为
( n+sizeof(int) )/sizeof(int) + sizeof(header),算出他的sizeof(int)的单位个数.更为细致的方法是,对传入的n值进行是否是该平台内存对齐值的倍数。


代码中的讲解
一些代码风格和算法大部分都按照书上的源码。只是对它做了详细的分析
整体数据结构的模拟:
和上部分的malloc完整模拟图差不多,不同的有三个地方
1、增加了一个链表头,不过不妨碍整个程序运行
2、没有not owned by malloc部分
3、一开始是一段的内存,经过几次使用和释放,模拟成模拟图里的使用情况

代码里和书中不同的地方如下:
1、结构头分配的大小不一样,导致计算方法有所偏差(因为我不知道这本书当时的机器环境)
2、一些算法上的比较,比如_malloc函数中判断是否满足大小的要求,要减去结构头的大小
3、自增了许多打印信息的调试,方便跟踪观察

结构体:
C++代码  
typedef union header   
{   /* block header */  
    struct   
    {  
        union header *ptr;              /* next block if on free list */  
        unsigned size;                  /* size of this block */  
    } s;  
    //Align align_x;                        /* force alignment of blocks */  
}Header;  

这里原先想和书上的内存对齐一致,但是没必要。不过却懒得再对已经写好代码的结构体修改

全局变量:
C++代码  
const int HEADER_LEN     = sizeof(header);     
const int LIST_LEN       = 200;                   /*空间的大小*/  
              
static void *empty_list   = malloc(LIST_LEN);       /*empty list to get started*/  
static Header *freep_list = NULL;                 /*start of free list*/  


函数说明:
C++代码  
void  _init();                                   /*初始,比如链表的结构头等*/  
void* _malloc (unsigned nbytes);                 /*分配*/  
void  _free   (void * pFree);                /*释放*/  
void  _debug_node  (const Header *const header);     /*打印一个结点的具体信息*/  
void  _debug_list  ();                       /*打印当前链表的信息*/  


代码具体说明:
我在源程序中一些地方也已经做了比较完整的注释
void _init():
创建一个表头(暂时没什么用),然后跳过该表头开始正式内存段。
C++代码  
/*表头*/  
pHeader->s.size = 0;  
pHeader->s.ptr  = pHeader+1;  
  
/*第一块内存表头,长度需要减去表头的长度*/  
pHeaderFirst = pHeader->s.ptr;  
pHeaderFirst->s.size = LIST_LEN-HEADER_LEN;  

因为这个时候内存只有一块,所以ptr为自己
pHeaderFirst->s.ptr  = pHeaderFirst;

void *_malloc(unsigned int nbytes):
在指针使用上是这样的,首先获得当前freep_list第一个结点pPrev,接着将pUsed的值设为pPrev的ptr值。这样虽然在第一次遍历会条过第一个结点,但是极大方便了内存的分配。在对pUsed这块内存进行判断大小时,采取的是best fit(详见前文描述)策略。如果刚好等于,则将pUsed+1返回当前可用内存,而pPrev的ptr设为pUsed的ptr,保持链表的循环;如果大于,则在pUsed的内存中划分出一段内存,对其设置好头部大小后返回,不做其他的操作.最后是判断是否循环到尾的操作
C++代码  
if( pUsed==freep_list->s.ptr )        /*wrapped around free list*/  
{  
    return NULL;  
}  

还记得我们刚才跳过的结点吗?刚好在第二次遍历完它的时候进行退出的判断

C++代码  
void _free(void * pFree):  
    /*跳过表头结构*/  
    pFree_list = freep_list->s.ptr;  
    for( ; !(pFree_list<pHeader && pHeader<pFree_list->s.ptr) ;   
           pFree_list = pFree_list->s.ptr)  
    {  
        if( pFree_list>=pFree_list->s.ptr &&   
            (pFree_list<pHeader || pHeader<pFree_list->s.ptr)  
          )  
        {  
            break;  
        }  
    }  


假设现在链表如下:(地址从低到高,循环链表)
3->4->6->8
这里分三种情况,分别是头、尾、中间,用以下值假设地址
pHeader={ 1,2,5,9,10 },
所以在for的条件中用pFree_list<pHeader && pHeader<pFree_list->s.ptr判断是否在两个结点的中间。如果是pFree=5;则很好理解。如果是1或者9则这条规则不适用,则以pFree_list>=pFree_list->s.ptr判断是否到尾部。其中,
C++代码  
/*         
*pFree_list == pFree_list->s.ptr,    表示只有一个结点  
*pFree_list > pFree_list->s.ptr,  表示大于1个结点  
*pHeader    < pFree_list->s.ptr,  表示插入头部  
*pFree_list < pHeader:           表示插入尾部  
/  

但接下来的代码很巧妙的完成了并入了过程,并没有用到pHeader<pFree_list->s.ptr和pFree_list<pHeader这两个条件,不过也给出了利用这两个条件的伪码.
C++代码  
if( (int)pHeader + pHeader->s.size == (int)pFree_list->s.ptr )  
{  
    pHeader->s.size += pFree_list->s.size;  
    pHeader->s.ptr  = pFree_list->s.ptr->s.ptr;  
}  
else  
{  
    pHeader->s.ptr = pFree_list->s.ptr;  
}  
  
if( (int)pFree_list + pFree_list->s.size == (int)pHeader )  
{  
    pFree_list->s.size += pHeader->s.size;  
    pFree_list->s.ptr  =  pHeader->s.ptr; /*这句我认为是多余的*/  
}  
else  
{  
    pFree_list->s.ptr  = pHeader;  
}  

这里以pFree为10为例子,最终我们要把它变成3->4->6->8->10的形式。这时的pFree_list=8.首先不满足第一个if,在else部分将自己的ptr指向3;在第二个if中,也不满足if的条件(这里只有9可以满足相邻的条件)。在else部分,将8的ptr设为10.这样就完成了一个结点的插入。即在这个循环链表里,对假设是头部的进行头部结点的操作;对假设是尾部的进行尾部结点的操作。恰好的完成了结点的并入。

C++代码  
/*
    这里给出另一段伪代码,根据pFree_list<pHeader || pHeader<pFree_list->s.ptr
    这两个条件来判断是在头部还是尾部。原来的代码更简练。
*/   
    //头部插入  
    if( pHeader < pFree_list->s.ptr )  
    {  
        if( pHeader + pHeader->s.size == pFree_list->s.ptr )  //刚好  
        {  
            pHeader->s.size += pFree_list->s.size;  
            pHeader->s.ptr  = pFree_list->s.ptr->s.ptr;  
        }  
        else  
        {  
            pHeader->s.ptr = pFree_list->s.ptr;  
            pFree_list->s.ptr  = pHeader;                    //看吧,只是多了一行代码  
        }  
    }  


这里只有9可以满足相邻的条件?
这里也是利用了地址相邻的特性。假设一段可用内存有100字节,头部占用8个字节。申请50个字节,那原有的可用字节只有100-8-50=42.即申请的内存从地址42开始,所以这是只要将可用内存加上自己的size判断是否会和下一段内存相等即可知相邻。然后将两段内存并入。换一句话说,内存的碎片也是这样产生的,如果中间(地址相邻)的内存不释放或者释放不正确,则下一个结点就无法进行归并,当然这个还有很多原因,起码这也是其中之一。


程序运行结果展示
终于到最后一个模块了。。
假设内存最早是200byte,我们申请10,15,20,25这几块内存。结果如下:
以*****************/做为分块表示
 
因为链表头部用了8个字节,所以第一块的size为192。从2-5块里,都是对分配到的结点信息。_malloc:nunits:20:10,:10表示我们需要的申请的内存数,20表示实际申请的内存数(包括一个结构头和内存对齐).

这时可用的链表中信息如下:
 
192-20-24-32-36=80

释放掉3和5块的内存
 

这个时候可用的链表中信息如下
 

为什么是116呢?注意,这个时候按顺序申请的内存实际是倒着链表,所以5块被认为是开头的点了,就自然的并入了



完整代码见 代码储备->malloc模拟

资料参考
《The C Programming_Language》  K&R
8.7 Example - A Storage Allocator
他写的代码,我等了几天才下到了,那个下载有限制。
我给大家贴出来。主要是大家进行对比。。。所以给大家看看 共同学习。
程序代码:
/*

 *windows2003+vc7.1.3091编译通过

 参考资料《The C Programming_Language》K&R  8.7 Example - A Storage Allocator

 @作者:lin_style

 @邮箱:lin_style@时间:2008-10-21 17:21:51

 @其他:一些代码风格都保留和书上一样,更详细的说明请看和本代码一起的文章。也可以来邮件索取
*/

#include <iostream>

using namespace std;
/* for alignment to long boundary */
typedef long Align; 
typedef union header 
{    /* block header */
    struct 
    {
        union header *ptr;        /* next block if on free list */
        unsigned size;            /* size of this block */
    } s;
    //Align align_x;                /* force alignment of blocks */
}Header;

const int HEADER_LEN     = sizeof(header);
const int LIST_LEN       = 200;      

                                            
static void *empty_list      = malloc(LIST_LEN); /*empty list to get started*/
static Header *freep_list = NULL;              /*start of free list*/

void  _init();
void* _malloc (unsigned nbytes);
void  _free   (void * pFree);
void  _debug_node  (const Header *const header);
void  _debug_list  ();


int main(int argc, char **argv)
{
    char *pC;
    int  *p1, *p2, *p3, *p4;  /*10 20 30 40 */

    _init();

    p1 = (int*)_malloc(10);
    if( NULL==p1 )
    {
        cout<<"p1 NULL"<<endl;
    }

    p2 = (int*)_malloc(15);
    if( NULL==p2 )
    {
        cout<<"p2 NULL"<<endl;
    }

    p3 = (int*)_malloc(20);
    if( NULL==p2 )
    {
        cout<<"p2 NULL"<<endl;
    }

    p4 = (int*)_malloc(25);
    if( NULL==p4 )
    {
        cout<<"p4 NULL"<<endl;
    }
    
    _debug_list();
    _free(p3);
    _free(p5);
    _debug_list();


    free(empty_list);

    return 0;
}

void _init()
{
    Header *pHeader;
    Header *pHeaderFirst;

    pHeader = (Header*)empty_list;

    /*表头*/
    pHeader->s.size = 0;
    pHeader->s.ptr  = pHeader+1;

    /*第一块内存表头,长度需要减去表头的长度*/
    pHeaderFirst = pHeader->s.ptr;
    pHeaderFirst->s.size = LIST_LEN-HEADER_LEN;
    
    /*wrapped around free list*/
    pHeaderFirst->s.ptr  = pHeaderFirst;

    freep_list = (Header *)empty_list;
    
    /*debug*/
    cout<<"_init() pHeader:"<<endl;
    _debug_node(pHeader);
    
    cout<<"_init() pHeaderFirst:"<<endl;
    _debug_node(pHeaderFirst);
    
}

void *_malloc(unsigned int nbytes)
{
    Header *pUsed, *pPrev;
    unsigned int nunits, _nunits;   /*_nunits只是打印用*/

    /*强制内存分配的对齐*/
    _nunits = nbytes;
    nunits = ( nbytes+sizeof(int) )/sizeof(int) ;
    nunits = nunits*sizeof(int) + HEADER_LEN;
    
    /*pUsed=pPrev->s.ptr,跳过表头结构*/
    pPrev = freep_list->s.ptr;
    
    for(pUsed = pPrev->s.ptr ; ; pPrev=pUsed, pUsed=pPrev->s.ptr )
    {
        if( pUsed->s.size-HEADER_LEN >= nunits )            /*big enough*/
        {
            if( pUsed->s.size-HEADER_LEN == nunits )        /*exactly*/
            {
                pUsed->s.size = nunits;
                pPrev->s.ptr = pUsed->s.ptr;
            }
            else
            {
                pUsed->s.size -= nunits;
                pUsed =(Header*)((int)pUsed + pUsed->s.size);
                pUsed->s.size = nunits;
            }

            /*debug*/
            cout<<"_malloc:nunits:"<<nunits<<":"<<_nunits<<endl;
            _debug_node(pUsed);

            return (void*)(pUsed+1);
        }
        if( pUsed==freep_list->s.ptr )        /*wrapped around free list*/
        {
            return NULL;
        }
    
    }

    return 0;
}
void _free(void * pFree)
{ 
    assert(pFree);

    Header *pHeader, *pFree_list;

    /*跳过表头结构*/
    pFree_list = freep_list->s.ptr;
    
    pHeader = (Header*)pFree-1;            /*回到该段内存结构头*/

    cout<<"_free:nunits:"<<pHeader->s.size<<endl;
    _debug_node(pHeader);

    /*
    1 2 3 4 5 6
    */

    for( ; !(pFree_list<pHeader && pHeader<pFree_list->s.ptr) ; 
           pFree_list = pFree_list->s.ptr)
    {
        /*free block at start or end of arena*/
        /*表示已经到末尾
         *pFree_list==pFree_list->s.ptr,表示只有一个结点
         *pFree_list>pFree_list->s.ptr,表示大于1个结点
         *
         *pHeader<pFree_list->s.ptr:表示插入头部
         *pFree_list<pHeader:表示插入尾部
        */
        if( pFree_list>=pFree_list->s.ptr && 
            (pFree_list<pHeader || pHeader<pFree_list->s.ptr)
          )
        {
            break;
        }
    }
    /*
    对插入尾部和头部分两种情况
    链表:2->3->4 
    情况1:假设是插入5,则把4加上自己的size,看是否和5的地址一样,如果是则插入
           假设是插入6,则直接把4的ptr指向6,再将6指向第一个结点
    
    下面这段代码是书上的源代码,写得很巧妙。还是以上面的做为讨论,假设插入5,
    在第一个if时不满足,但是却将6的ptr指向了第一个结点,接着在第二if判断时,
    4将自己的ptr指向6,而此时因为6在上一回的ptr中已经完成了对头结点的指向。
    即在这个循环链表里,对假设是头部的进行头部结点的操作;对假设是尾部的进行
    尾部结点的操作
    */

    /*join to start of arena*/
    if( (int)pHeader + pHeader->s.size == (int)pFree_list->s.ptr )
    {
        pHeader->s.size += pFree_list->s.size;
        pHeader->s.ptr  = pFree_list->s.ptr->s.ptr;
    }
    else
    {
        pHeader->s.ptr = pFree_list->s.ptr;
    }

    /*join to end of arena*/
    if( (int)pFree_list + pFree_list->s.size == (int)pHeader )
    {
        pFree_list->s.size += pHeader->s.size;
        pFree_list->s.ptr  =  pHeader->s.ptr; /*这句我认为是多余的*/
    }
    else
    {
        pFree_list->s.ptr  = pHeader;
    }

    /*
    这里给出另一段伪代码,根据pFree_list<pHeader || pHeader<pFree_list->s.ptr
    这两个条件来判断是在头部还是尾部。原来的代码更简练。
    
    //头部
    if( pHeader < pFree_list->s.ptr )
    {
        if( pHeader + pHeader->s.size == pFree_list->s.ptr )//刚好
        {
            pHeader->s.size += pFree_list->s.size;
            pHeader->s.ptr  = pFree_list->s.ptr->s.ptr;
        }
        else
        {
            pHeader->s.ptr = pFree_list->s.ptr;
            pFree_list->s.ptr  = pHeader;            //看吧,只是多了一行代码
        }
    }
    //尾部
    else if( pFree_list < pHeader )
    {
        
    }
    */

}


void  _debug_node  (const Header *const header)
{
    assert(header);

    cout<<"_debug_node:"<<endl;
    cout<<"address:"<<header<<endl;
    cout<<"s.ptr  :"<<header->s.ptr<<endl;
    cout<<"s.size :"<<header->s.size<<endl;
    cout<<"****************************/"<<endl<<endl;
}

void  _debug_list  ()
{
    const Header *header = freep_list->s.ptr;
    cout<<"_debug_list:"<<endl;
    do
    {
        cout<<"address:"<<header<<endl;
        cout<<"s.ptr  :"<<header->s.ptr<<endl;
        cout<<"s.size :"<<header->s.size<<endl<<endl;

        header = header->s.ptr;
    }while( header!=freep_list->s.ptr );
    cout<<"****************************/"<<endl<<endl;

}


[ 本帖最后由 小鱼儿c 于 2011-9-21 23:26 编辑 ]

用心做一件事情就这么简单
2011-09-21 23:18
饭桶
Rank: 6Rank: 6
等 级:侠之大者
帖 子:165
专家分:422
注 册:2011-4-5
收藏
得分:8 

人得一生得奋斗!
2011-09-21 23:18
小鱼儿c
Rank: 9Rank: 9Rank: 9
等 级:贵宾
威 望:14
帖 子:852
专家分:1317
注 册:2011-4-1
收藏
得分:0 
通过这章我们知道。
①用malloc是向系统要的。如果你没有释放,系统是不会收回(除一般在你关掉程序的时候)。
所以这个malloc依靠系统,这里只是简单的模拟。据说源代码就几千行。。。
②可以看出free释放也是有要求的。一定是你申请的起始位置。系统会检测。你可以试试。
所以你修改malloc的指针的时候一定要注意,否者释放为报错。。貌似hellovfp提过类似的问题。。。。hellovfp是我师傅哦。。。
③看出来系统给我们用malloc内存比我们实际要求的大。为了放块控制信息或减少碎片。。。。

用心做一件事情就这么简单
2011-09-21 23:44
零波丽
Rank: 3Rank: 3
来 自:莉莉斯
等 级:论坛游侠
帖 子:222
专家分:107
注 册:2011-9-8
收藏
得分:8 
留言 有时间看看~
零波 丽 说道

人又是什么?神造出来的东西。 人是人造出来的东西。
2011-09-22 00:25
yangli0314
Rank: 3Rank: 3
来 自:重庆
等 级:论坛游侠
帖 子:101
专家分:142
注 册:2011-9-3
收藏
得分:8 
楼主好厉害,顶一个!
2011-09-22 06:33
embed_xuel
Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19
等 级:贵宾
威 望:58
帖 子:3845
专家分:11385
注 册:2011-9-13
收藏
得分:8 
回复 楼主 小鱼儿c
嗯,c->数据结构->操作系统,学C进阶结合操作系统是王道
收到的鲜花
  • 小鱼儿c2011-09-22 14:21 送鲜花  2朵   附言:我很赞同

总有那身价贱的人给作业贴回复完整的代码
2011-09-22 07:11
hellovfp
Rank: 16Rank: 16Rank: 16Rank: 16
等 级:禁止访问
威 望:30
帖 子:2976
专家分:7697
注 册:2009-7-21
收藏
得分:8 
不错,理解malloc和free背后的机理,有助于你的学习提高。
Share your technology knowledge, you will imporve youself!
偶不是你师博啦,只是和你一样,把偶所了解的知识共享给你罢了,学习知识的快乐源于共享,明白了解就好。
众生皆平等,我们都是世界上渺小的人类。
收到的鲜花
  • 小鱼儿c2011-09-22 14:22 送鲜花  5朵   附言:呵呵,给你分分。。。。。

我们都在路上。。。。。
2011-09-22 11:52
快速回复:小鱼儿の模拟内存动态分配学习
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.026232 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved