简述动态存储分配及malloc(),free()函数(针对linux)

首先说一下程序运行是的存储分配:

存储分配
存储分配

这张是典型的C语言的存储分配图。动态存储分配主要涉及图中的堆区。堆是无结构的连续的存储区域。当调用malloc()函数时,存储分配器从堆中找一块合适大小的连续的内存空间返回给程序。

malloc和free函数的原型如下:

void * malloc(size_t size)

void free(void* ptr)

这两个函数的使用就不在赘述了。

其实说白了,malloc和free函数就是负责对堆区的存储空间进行管理。既然要对堆区进行管理,而堆区又是无结构的,因此需要一个(运行库)自定义的结构来对堆区进行管理。

既然是对存储空间的管理,那么必然要面对两个问题--------存储空间的利用率和运行的效率。

首先是存储空间的利用率,这里面牵扯到一个概念--------碎片(fragmentation)。碎片又分为两种:内碎片和外碎片。

  1. 内碎片是指分配给程序的存储空间没有用完,有一部分是闲着的,这部分空间成为内碎片。
  2. 外碎片是虽然还有空间可以分配,但是这些空间都是不连续的小块,不能满足程序对连续存储的要求,因此无法分配给程序使用。这些不能满足程序要求的小的可以分配的存储空间成为外碎片。

对内碎片的处理很容易,只要保证分配的空间和使用的空间一样大小就行了。这主要是靠写程序的程序员们估算好自己所需要的空 间。(为了保证分配的空间是块对齐,也就是双字对齐,分配器可能返回比实际申请的空间多出一个字节的空间。但相对于目前庞大的内存空间,这一个字节已经微 不足道了。但是如果程序申请大量的小块内存,这个问题还是要考虑的。。。比如:大量申请3个字节的空间,为了对齐,返回四个字节。这就导致四分之一的空间 浪费。)

对于外碎片,处理的方法说起来很简单--------合并。但说归说,真正合并的时候就很困难。如果空闲的碎片是连续的, 那合并很简答。如果碎片不连续,这就很麻烦了。可以对内存进行紧缩,但这样效率很难保证。操作系统对内存进行管理是,大都使用分页,分段或段页式的管理来 减少外碎片。如果在程序中对堆区的管理再引入复杂的外碎片处理措施,不但运行效率低,而且多此一举。。。所以,当在处理合并的时候,基本都只考虑相邻的空 闲块。

关于效率的问题,后面会详细说明。

如果空间无限大,那么malloc就可以不断的分配新的空间给程序,而free函数则什么都不要做。因此,使用过的空间将直接丢弃而不再重用。这样 malloc和free函数即简单,效率也高。但是!存储空间是有限的。理论上在 32-位 x86 系统上,进程可用的最大空间是4G(由地址宽度决定)。但是不可能直接给程序分配4G的空间。。。因此,程序一开始运行的时候,堆区都有一个默认的大小。 这样,在程序运行的过程中,可能出现堆区中的空间都耗尽的情况(也可能没有耗尽,但都是小的碎片)。因此,就要给程序的堆区扩容,也就是让操 作系统给程序分配更多的空间,术语叫把空间映射到程序的堆区中。在这里有一个概念,叫系统中断点或当前中断点,也就是被映射的内存的边界(最后一个有效地 址),也就是堆顶。

对堆区进行扩容,可调用下面的两个系统调用:

  • brk: int brk(void *end_data_segment) 是一个非常简单的系统调用。  brk() 只是简单地将系统中断点设置为end_data_segment,向进程添加内存或者从进程取走内存。
  • mmap: mmap(),或者说是“内存映像”,类似于 brk(),但是更为灵活。首先,它可以映射任何位置的内存, 而不单单只局限于进程。其次,它不仅可以将虚拟地址映射到物理的 RAM 或者 swap,它还可以将 它们映射到文件和文件位置,这样,读写内存将对文件中的数据进行读写。不过,在这里,我们只关心 mmap 向进程添加被映射的内存的能力。 munmap() 所做的事情与mmap() 相反。

还有一个叫sbrk的系统调用void *sbrk(intptr_t increment)。sbrk()增加increment字节的内存到进程的堆区。而其返回的是旧的系统中断点,而不是新的。

下面说一下一种叫做隐式空闲链表的管理堆区的方法。堆块的格式如下:

-------------------------------------------------------------------

|         int         available        /*是否空闲*/                                |

|         int         size                   /*此块的大小*/                             |

------------------------------------------------------------------

|                                                                                                 | <----malloc返回的指针指向此处

|                      实际空闲内存区                                                     |

|                                                                                            |

------------------------------------------------------------------

|                       填充区(可选)                                                   |

------------------------------------------------------------------

available 表示此块是否可用(被分配)。为1时表示可以分配,为0时表示被占用。

size表示此块内存的大小。

填充区用来进行块对齐(双字对齐)。

当调用malloc进行内存分配的时候,需要从空闲的内存块中选择一块合适的。选择的方法常用的有三种:首次匹配,下一次匹配,最佳匹配。

首次匹配:从头开始搜索表,选择第一个合适的块。

下一次匹配:和首次匹配类似,但是不是每次都从头搜索,而是从上一次搜索结束的位置开始搜索。

最佳匹配:选择所有合适的空闲块中最小的一块。

下一次匹配的运行速度要比首次匹配快,但空间利用率没有首次匹配高。最佳匹配的空间利用率最高,但运行也是最慢的。

当搜索到合适的空闲块后,这个空闲块往往不会整好合适,基本上都会大一下。多余的部分如果也分配给程序,就造成了内碎片。如果不分配,就要对空闲块进行分割。将多余的部分重新组成一个空闲块。这就有可能造成外碎片。因此,到底怎样处理,要看运行库的实现了。

若两个空闲块整好是连着的,那么把这两个块合并成一个块就可以提高空间的利用率。如果当前块的后一个块是空闲块,那么根据当前块的指针和其长度,很容易得到下一个空闲块的位置指针。但当空闲块在当前块的前面时,就很难获得其指针了。Knuth提出了一种聪明而有效的方法,叫做边界标记。就是复制块头到空闲块的尾部,形成一个块尾。这样,只需将当前块的指针向前移动一个块头大小的距离,就可以得到指向前一个块的块尾的指针,这样,前一个块的所有信息就都得到了。

在实际的实现中,往往引入两个特殊的块------序言块和结尾块。

序言块是程度为零且一直标记为不可分配的块,在整个堆区的最下面(堆区的开始位置)。序言块有块头和块尾。

结尾块在堆区的最上面(堆区的结束位置),也是长度为零且一直为不可分配。结尾块只有块头。

这两个块的引入可以减少很多边界的处理,提高程序的简洁性和效率。

下面是一个简单的实现的例子:

采用首次匹配。

复制代码
  1  /*  
  2   * File:   my_alloc.h
  3    */
  4 
  5  #ifndef _MY_ALLOC_H
  6  #define     _MY_ALLOC_H
  7 
  8  #ifdef    __cplusplus
  9  extern   " C "
 10  {
 11  #endif
 12 
 13  #include  < unistd.h >
 14  #define  INIT_S 10240  // 堆区的默认大小
 15       int  is_initialized  =   0 // 标记是否已经初始化
 16       void   * memory_start;  // 堆区的开始地址
 17       void   * memory_end;  // 系统中断点
 18 
 19       /*
 20       * 内存控制块
 21       * 块头和块尾的结构
 22        */
 23      typedef  struct  __mem_control_block
 24      {
 25           int  available;  // 是否可用
 26           int  size;  // 大小
 27      } mem_control_block;
 28 
 29       /*
 30        * 初始化堆区。
 31        * 建立堆区的初始结构,设置序言块,结尾块等。
 32         */
 33       void  my_malloc_init()
 34      {
 35           /*
 36           * 获得系统中断点,也即堆区的堆顶
 37            */
 38          memory_end  =  sbrk( 0 );
 39           /*
 40           * 堆区的开始位置
 41            */
 42          memory_start  =  memory_end;
 43           /*
 44           * 为堆区预先分配INIT_S个字节。
 45            */
 46           if  (sbrk(INIT_S)  <=   0 )
 47          {
 48              printf( " Init Error!\n " );
 49               return ;
 50          }
 51          memory_end  +=  INIT_S;
 52 
 53          mem_control_block  * mcb;
 54           /* 序言块 */
 55           /* 块头 */
 56          mcb  =  memory_start;
 57          mcb  ->  available  =   0 ;
 58          mcb  ->  size  =   0 ;
 59           /* 块尾 */
 60          mcb  =  ( void * )mcb +   sizeof (mem_control_block);
 61          mcb  ->  available  =   0 ;
 62          mcb  ->  size  =   0 ;
 63          
 64           /* 设置控制块, 块头 */
 65          mcb  =  memory_start  +   2   *   sizeof (mem_control_block);
 66          mcb  ->  available  =   1 ;
 67           /* 这个块的长度为整个堆区(除去控制块的长度) */
 68          mcb  ->  size  =  INIT_S  -   5   *   sizeof  (mem_control_block);
 69           /* 设置边界标记,块尾 */
 70          mcb  =  memory_end  -   2   *   sizeof  (mem_control_block);
 71          mcb  ->  available  =   1 ;
 72          mcb  ->  size  =  INIT_S  -   5   *   sizeof  (mem_control_block);
 73 
 74 
 75           /* 结尾块 */
 76          mcb  =  memory_end  -   sizeof  (mem_control_block);
 77          mcb  ->  available  =   0 ;
 78          mcb  ->  size  =   0 ;
 79 
 80           /*
 81           * 已经进行了初始化
 82            */
 83          is_initialized  =   1 ;
 84      }
 85 
 86       /*
 87        * 模拟malloc函数
 88         */
 89       void   *  my_malloc( int  size)
 90      {
 91           if  ( ! is_initialized)
 92          {
 93              my_malloc_init();
 94          }
 95 
 96           /* 分配的内存块的地址 */
 97           void   *  mem_location  =   0 ;
 98 
 99           /* 内存控制块的指针 */
100          mem_control_block  * curr_mcb,  * tail;
101 
102           /* 从头开始遍历 */
103          curr_mcb  =  memory_start;
104 
105           while  (( void * )curr_mcb  <  memory_end)
106          {
107               if  (curr_mcb  ->  available) /* 找到一个空闲块 */
108              {
109                   if  (curr_mcb  ->  size  >=  size  &&  curr_mcb  ->  size  <  size  +   2   *   sizeof  (mem_control_block))
110                       /* 大小合适,多余的空间不够进行分割的。也就是剩余的空间无法满足块头和块尾所需要的空间。 */
111                  {
112                       /* 获得返回的内存地址 */
113                       /*
114                            *     此处必须把curr_mcb转成void*的格式!!
115                            *  否则,在加的时候是以sizeof(mem_control_block)的倍数增加,而不是仅仅加一!!
116                             */
117                      mem_location  =  ( void   * ) curr_mcb  +   sizeof  (mem_control_block);
118                       /* 标记已经占用 */
119                      curr_mcb  ->  available  =   0 ;
120 
121                       /* 获得边界标记的指针,块尾 */
122                      tail  =  ( void   * ) curr_mcb  +   sizeof  (mem_control_block)  +  curr_mcb  ->  size;
123 
124                       /* 标记已经占用 */
125                      tail  ->  available  =   0 ;
126                       break ;
127                  }
128                   else   if  (curr_mcb  ->  size  >  size  +   2   *   sizeof  (mem_control_block))
129                       /* 进行分割 */
130                  {
131                       int  old_size  =  curr_mcb  ->  size;
132                       /* 获得分配的内存块的地址 */
133                      mem_location  =  ( void   * ) curr_mcb  +   sizeof  (mem_control_block);
134                       /* 标记已经占用 */
135                      curr_mcb  ->  available  =   0 ;
136                      curr_mcb  ->  size  =  size;
137                       /* 获得边界标记的指针,块尾 */
138                      tail  =  ( void   * ) curr_mcb  +   sizeof  (mem_control_block)  +  size;
139                       /* 标记已经占用 */
140                      tail  ->  available  =   0 ;
141                      tail  ->  size  =  size;
142 
143                       /* 将余下的部分分割成新的空闲块 */
144                      mem_control_block  * hd,  * tl;  /* 新块的块头和块尾 */
145                       /* 块头 */
146                      hd  =  ( void   * ) tail  +   sizeof  (mem_control_block);
147                      hd  ->  available  =   1 ;
148                      hd  ->  size  =  old_size  -  size  -   2   *   sizeof  (mem_control_block);
149                       /* 块尾 */
150                      tl  =  ( void   * ) hd  +  hd  ->  size  +   sizeof  (mem_control_block);
151                      tl  ->  available  =   1 ;
152                      tl  ->  size  =  hd  ->  size;
153 
154                       break ;
155                  }
156              }
157 
158               /* 指向下一个块 */
159              curr_mcb  =  ( void * ) curr_mcb  +  curr_mcb  ->  size  +   2   *   sizeof  (mem_control_block);
160          }
161 
162           /* 没有找到合适的块,则扩展堆区,分配合适大小的内存加到堆区 */
163           if  ( ! mem_location)
164          {
165               /* 申请空间 */
166               if  (sbrk(size  +   2   *   sizeof  (mem_control_block))  <=   0 )
167              {
168                  printf( " Sbrk Error!\n " );
169                   return   0 ;
170              }
171               /* 设置控制块的信息 */
172              curr_mcb  =  ( void * )memory_end  -   sizeof (mem_control_block);
173              curr_mcb  ->  available  =   0 ;
174              curr_mcb  ->  size  =  size;
175               /* 设置边界标记,块尾的信息 */
176              tail  =  ( void * ) curr_mcb  +  curr_mcb  ->  size  +   sizeof  (mem_control_block);
177              tail  ->  available  =   0 ;
178              tail  ->  size  =  size;
179 
180               /* 获得分配的内存块的地址 */
181              mem_location  =  ( void * )curr_mcb  +   sizeof  (mem_control_block);
182 
183              memory_end  =   memory_end  +  size  +   2   *   sizeof  (mem_control_block);
184                /* 设置结尾块 */
185              tail  =  ( void * )memory_end  -   sizeof (mem_control_block);
186              tail  ->  available  =   0 ;
187              tail  ->  size  =   0 ;
188          }
189 
190           return  mem_location;
191      }
192       /*
193       * 模拟free函数
194        */
195       void  my_free( void   * ptr)
196      {
197           if  (ptr  <=   0 )
198          {
199               return ;
200          }
201 
202          mem_control_block  * curr;
203           /* 指向控制块的地址 */
204          curr  =  ptr  -   sizeof  (mem_control_block);
205 
206          
207 
208           /* 标记为空闲,可用 */
209          curr -> available  =   1 ;
210           /*
211           * 合并
212            */
213          mem_control_block  * pre,  * next,  * tmp;
214          
215           /* 获得前一个块的块头地址 */
216          pre  =  ptr  -   2   *   sizeof  (mem_control_block); /* 这条语句获得了前一个块的块尾的地址 */
217          pre  =  ( void   * ) pre  -  pre  ->  size  -   sizeof  (mem_control_block); /* 进一步计算块头的地址 */
218 
219           /* 获得后一个块的块头地址 */
220          next  =  ptr  +  curr  ->  size  +   sizeof  (mem_control_block);
221 
222           if  ( ! pre  ->  available  &&  next  ->  available) /* 只有后一个块空闲 */
223          {
224              curr  ->  size  +=  (next  ->  size  +   2   *   sizeof  (mem_control_block));
225               /* 设置块尾 */
226              tmp  =  ( void   * ) curr  +  curr  ->  size  +   sizeof  (mem_control_block);
227              tmp  ->  available  =   1 ;
228              tmp  ->  size  =  curr  ->  size;
229          }
230           else   if  (pre  ->  available  &&   ! next  ->  available) /* 只有前一个块空闲 */
231          {
232              pre  ->  size  +=  (curr  ->  size  +   2   *   sizeof  (mem_control_block));
233               /* 设置块尾 */
234              tmp  =  ( void   * ) pre  +  pre  ->  size  +   sizeof  (mem_control_block);
235              tmp  ->  available  =   1 ;
236              tmp  ->  size  =  pre  ->  size;
237          }
238           else   if  (pre  ->  available  &&  next  ->  available) /* 前后都块空闲 */
239          {
240              pre  ->  size  +=  (curr  ->  size  +   4   *   sizeof  (mem_control_block)  +  next  ->  size);
241               /* 设置块尾 */
242              tmp  =  ( void   * ) pre  +  pre  ->  size  +   sizeof  (mem_control_block);
243              tmp  ->  available  =   1 ;
244              tmp  ->  size  =  pre  ->  size;
245          }
246 
247           return ;
248 
249      }
250 
251       void  print_info()
252      {
253          printf( " printf_info:\n " );
254          mem_control_block  * curr  =  memory_start;
255           while  (( void * )curr  <  memory_end)
256          {
257 
258              printf( " a? %d s: %d %d %d\n " , curr  -> available, curr  -> size, memory_end, curr);
259              curr  =  ( void * ) curr  +  curr  ->  size  +   2   *   sizeof  (mem_control_block);
260          }
261      }
262 
263 
264  #ifdef    __cplusplus
265  }
266  #endif
267 
268  #endif     /* _MY_ALLOC_H */
269 
270 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值