malloc实现原理Word格式文档下载.docx
《malloc实现原理Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《malloc实现原理Word格式文档下载.docx(42页珍藏版)》请在冰豆网上搜索。
intcount,*array;
/*count是一个计数器,array是一个整型指针,也可以理解为指向一个整型数组的首地址*/
if((array(int*)malloc(10*sizeof
(int)))==NULL)
printf("
不能成功分配存储空间。
"
);
exit
(1);
}
for(count=0;
count〈10;
count++)/*给数组赋值*/
array[count]=count;
for(count=0;
count++)/*打印数组元素*/
%2d"
array[count]);
上例中动态分配了10个整型存储区域,然后进行赋值并打印。
例中if((array(int*)malloc(10*sizeof(int)))==NULL)语句可以分为以下几步:
1)分配10个整型的连续存储空间,并返回一个指向其起始地址的整型指针
2)把此整型指针地址赋给array
3)检测返回值是否为NULL
3.malloc()工作机制
malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。
调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。
然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。
接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。
调用free函数时,它将用户释放的内存块连接到空闲链上。
到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。
于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。
4.malloc()在操作系统中的实现
在C程序中,多次使用malloc()和free()。
不过,您可能没有用一些时间去思考它们在您的操作系统中是如何实现的。
本节将向您展示malloc和free的一个最简化实现的代码,来帮助说明管理内存时都涉及到了哪些事情。
在大部分操作系统中,内存分配由以下两个简单的函数来处理:
void*malloc(longnumbytes):
该函数负责分配numbytes大小的内存,并返回指向第一个字节的指针。
voidfree(void*firstbyte):
如果给定一个由先前的malloc返回的指针,那么该函数会将分配的空间归还给进程的“空闲空间”。
malloc_init将是初始化内存分配程序的函数。
它要完成以下三件事:
将分配程序标识为已经初始化,找到系统中最后一个有效内存地址
,然后建立起指向我们管理的内存的指针。
这三个变量都是全局变量
:
清单1.我们的简单分配程序的全局变量
inthas_initialized=0;
void*managed_memory_start;
void*last_valid_address;
如前所述,被映射的内存的边界(最后一个有效地址)常被称为系统中断点或者当前中断点。
在很多
UNIX
?
系统中,为了指出当前系统中断点,必须使用sbrk(0)函数。
sbrk根据参数中给出的字节数移动当前系统中断点,然后返回新的系统中断点。
使用参数0只是返回当前中断点。
这里是我们的malloc初始化代码,它将找到当前中断点并初始化我们的变量:
清单2.分配程序初始化函数
/*Includethesbrkfunction*/
#include
voidmalloc_init()
/*
grab
thelastvalidaddressfromtheOS*/
last_valid_address=sbrk(0);
/*wedon'
thaveanymemorytomanageyet,so
*justsetthebeginning
tobe
last_valid_address
*/
managed_memory_start=last_valid_address;
/*Okay,we'
reinitializedandreadytogo*/
has_initialized=1;
现在,为了完全地管理内存,我们需要能够追踪要分配和回收哪些内存。
在对内存块进行了free调用之后,我们需要做的是诸如将它们标记为未被使用的等事情,并且,在调用malloc时,我们要能够定位未被使用的内存块。
因此,malloc返回的每块内存的起始处首先要有这个结构:
清单3.内存控制块结构定义
structmem_control_block{
intis_available;
intsize;
};
现在,您可能会认为当程序调用malloc时这会引发问题——它们如何知道这个结构?
答案是它们不必知道;
在返回指针之前,我们会将其移动到这个结构之后,把它隐藏起来。
这使得返回的指针指向没有用于任何其他用途的内存。
那样,从调用程序的角度来看,它们所得到的全部是空闲的、开放的内存。
然后,当通过free()将该指针传递回来时,我们只需要倒退几个内存字节就可以再次找到这个结构。
在讨论分配内存之前,我们将先讨论释放,因为它更简单。
为了释放内存,我们必须要做的惟一一件事情就是,获得我们给出的指针,回退sizeof(structmem_control_block)个字节,并将其标记为可用的。
这里是对应的代码:
清单4.解除分配函数
voidfree(void*firstbyte){
structmem_control_block*mcb;
/*Backupfromthegivenpointertofindthe
*mem_control_block
mcb=firstbyte-sizeof(structmem_control_block);
/*Marktheblockasbeingavailable*/
mcb->
is_available=1;
/*That'
sIt!
We'
redone.*/
return;
如您所见,在这个分配程序中,内存的释放使用了一个非常简单的机制,在固定时间内完成内存释放。
分配内存稍微困难一些。
以下是该算法的略述:
清单5.主分配程序的伪代码
1.Ifourallocatorhasnotbeeninitialized,initializeit.
2.Addsizeof(structmem_control_block)tothesizerequested.
3.startatmanaged_memory_start.
4.Areweatlast_validaddress?
5.If
weare
:
A.Wedidn'
tfindanyexistingspacethatwaslargeenough
--asktheoperatingsystemformoreandreturnthat.
6.Otherwise:
A.Isthecurrentspaceavailable(checkis_availablefrom
themem_control_block)?
B.Ifitis:
i)Isitlargeenough(check"
size"
fromthe
mem_control_block)?
ii)Ifso:
a.Markitasunavailable
b.Movepastmem_control_blockandreturnthe
pointer
iii)Otherwise:
a.Moveforward"
bytes
b.Gobackgostep4
C.Otherwise:
i)Moveforward"
ii)Gobacktostep4
我们主要使用连接的指针遍历内存来寻找开放的内存块。
这里是代码:
清单6.主分配程序
void*malloc(longnumbytes){
/*Holdswherewearelookinginmemory*/
void*current_location;
/*Thisisthesameascurrent_location,butcasttoa
*memory_control_block
structmem_control_block*current_location_mcb;
/*Thisisthememorylocation
wewill
return.Itwill
*besetto0untilwefindsomethingsuitable
void*memory_location;
/*Initializeifwehaven'
talreadydoneso*/
if(!
has_initialized){
malloc_init();
/*Thememorywesearchforhastoincludethememory
*controlblock,buttheusersofmallocdon'
tneed
*toknowthis,sowe'
lljustadditinforthem.
numbytes=numbytes+sizeof(structmem_control_block);
Set
memory_locationto0untilwefindasuitable
*location
memory_location=0;
Begin
searchingatthestartofmanagedmemory*/
current_location=managed_memory_start;
/*Keepgoinguntilwehavesearchedallallocatedspace*/
while(current_location!
=last_valid_address)
/*current_locationandcurrent_location_mcbpoint
*tothesameaddress.However,current_location_mcb
*isofthecorrecttype,sowecanuseitasastruct.
*current_locationisavoidpointersowecanuseit
*tocalculateaddresses.
current_location_mcb=
(structmem_control_block*)current_location;
if(current_location_mcb->
is_available)
size>
=numbytes)
/*Woohoo!
vefoundanopen,
*appropriately-sizelocation.
/*Itisnolongeravailable*/
current_location_mcb->
is_available=0;
/*Weownit*/
memory_location=current_location;
Leave
theloop*/
break;
/*If
wemadeit
here,it'
sbecausetheCurrentmemory
*blocknotsuitable;
moveto
thenext
one
current_location=current_location+
size;
/*Ifwestilldon'
thaveavalidlocation,we'
ll
*havetoasktheoperatingsystemformorememory
memory_location)
/*Movetheprogrambreaknumbytesfurther*/
sbrk(numbytes);
/*Thenewmemorywillbewherethelastvalid
*addressleftoff
memory_location=last_valid_address;
/*We'
llmovethelastvalidaddressforward
*numbytes
last_valid_address=last_valid_address+numbytes;
/*Weneedtoinitializethemem_control_block*/
current_location_mcb=memory_location;
size=numbytes;
/*Now,nomatterwhat(well,exceptforerrorconditions),
*memory_locationhastheaddressofthememory,including
*themem_control_block
/*Movethepointerpastthemem_control_block*/
memory_location=memory_location+sizeof(structmem_control_block);
/*Returnthepointer*/
returnmemory_location;
这就是我们的内存管理器。
现在,我们只需要构建它,并在程序中使用它即可。
5.malloc()的其他实现
malloc()的实现有很多,这些实现各有优点与缺点。
在设计一个分配程序时,要面临许多需要折衷
的选择,其中包括:
分配的速度。
回收的速度。
有线程的环境的行为。
内存将要被用光时的行为。
局部缓存。
簿记(Bookkeeping)内存开销。
虚拟内存环境中的行为。
小的或者大的对象。
实时保证。
每一个实现都有其自身的优缺点集合。
在我们的简单的分配程序中,分配非常慢,而回收非常快。
另外,由于它在使用虚拟内存系统方面较差,所以它最适于处理大的对象。
还有其他许多分配程序可以使用。
其中包括:
DougLeaMalloc:
DougLeaMalloc实际上是完整的一组分配程序,其中包括DougLea的原始分配程序,GNU
libc分配程序和ptmalloc。
DougLea的分配程序有着与我们的版本非常类似的基本结构,但是它加入了索引,这使得搜索速度更快,并且可以将多个没有被使用的块组合为一个大的块。
它还支持缓存,以便更快地再次使用最近释放的内存。
ptmalloc是DougLeaMalloc的一个扩展版本,支持多线程。
在本文后面的
参考资料
部分中,有一篇描述DougLea的Malloc实现的文章。
BSD
Malloc:
BSDMalloc是随4.2BSD发行的实现,包含在
FreeBSD
之中,这个分配程序可以从预先确实大小的对象构成的池中分配对象。
它有一些用于对象大小的size类,这些对象的大小为2的若干次幂减去某一常数。
所以,如果您请求给定大小的一个对象,它就简单地分配一个与之匹配的size类。
这样就提供了一个快速的实现,但是可能会浪费内存。
在参考资料部分中,有一篇描述该实现的文章。
Hoard:
编写Hoard的目标是使内存分配在多线程环境中进行得非常快。
因此,它的构造以锁的使用为中心,从而使所有进程不必等待分配内存。
它可以显著地加快那些进行很多分配和回收的多线程进程的速度。
众多可用的分配程序中最有名的就是上述这些分配程序。
如果您的程序有特别的分配需求,那么您可能更愿意编写一个定制的能匹配您的程序内存分配方式的分配程序。
不过,如果不熟悉分配程序的设计,那么定制分配程序通常会带来比它们解决的问题更多的问题。
附:
C++中内存的动态分配与管理永远是一个让C++开发者头痛的问题,本文通过对C++中内存的动态分配释放的基本原理的介绍,让读者朋友能对C++中的内存的动态分配与释放有较为深入的理解,从而更好驾驭C++程序。
1.函数(Function)
(1)operatornewfunction
1
2
[cpp]
viewplaincopy
1.<
span
style="
color:
#0000ff;
>
void<
/span>
2.<
br>
3.
<
#000040;
*<
4.<
5.
#008080;
6.<
7.<
#007788;
operator<
8.<
9.
#0000dd;
new<
10.<
11.<
#008000;
(<
12.<
13.<
size_t<
14.<
15.<
)<
16.<
17.<
;
18.<
19.
#666666;
//Global<
20.<
21.<
22.<
23.<
24.<
25.
26.<
27.
class<
-<
28.<
29.nam