C和Cpp内存分配方式 (1)
![C和Cpp内存分配方式 (1)](https://img.360docs.net/imgdd/1pveu2w1lijs5h8y6tc8pedmuj752en1-d1.webp)
![C和Cpp内存分配方式 (1)](https://img.360docs.net/imgdd/1pveu2w1lijs5h8y6tc8pedmuj752en1-72.webp)
C/C++ 内存分配方式,堆区,栈区,new/delete/malloc/free
内存分配方式
内存分配方式有三种:
[1] 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
[2] 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
[3] 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,频繁地分配和释放不同大小的堆空间将会产生堆内碎块。
2. 程序的内存空间
一个程序将操作系统分配给其运行的内存块分为 4 个区域,如下图所示。
代码区(code area) 程序内存空间
全局数据区(data area)
堆区(heap area)
栈区(stack area)
一个由C/C++ 编译的程序占用的内存分为以下几个部分,
1 、栈区(stack )由编译器自动分配释放,存放为运行函数而分配的局部变量、函数参数、返回数据、返回地址等。其操作方式类似于数据结构中的栈。
2 、堆区(heap )一般由程序员分配释放,若程序员不释放,程序结束时可能由OS 回收。分配方式类似于链表。
3 、全局区(静态区)(static )存放全局变量、静态数据、常量。程序结束后有系统释放
4 、文字常量区常量字符串就是放在这里的。程序结束后由系统释放。
5 、程序代码区存放函数体(类成员函数和全局函数)的二进制代码。
下面给出例子程序,
int a = 0; // 全局初始化区
char *p1; // 全局未初始化区
int main() {
int b; // 栈
char s[] = \"abc\"; // 栈
char *p2; // 栈
char *p3 = \"123456\"; //123456\\0 在常量区,p3 在栈上。
static int c =0;// 全局(静态)初始化区
p1 = new char[10];
p2 = new char[20];
// 分配得来得和字节的区域就在堆区。
strcpy(p1, \"123456\"); //123456\\0 放在常量区,编译器可能会将它与p3 所指向的\"123456\" 优化成一个地方。
}
堆与栈的比较
1 申请方式
stack: 由系统自动分配。例如,声明在函数中一个局部变量int b; 系统自动在栈中为b 开
heap: 需要程序员自己申请,并指明大小,在C 中malloc 函数,C++ 中是new 运算符。如p1 = (char *)malloc(10); p1 = new char[10];
如p2 = (char *)malloc(10); p2 = new char[20];
但是注意p1 、p2 本身是在栈中的。
2 申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。
对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete 语句才能正确的释放本内存空间。
由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
3 申请大小的限制
栈:在Windows 下, 栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS 下,栈的大小是2M (也有的说是1M ,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow 。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
4 申请效率的比较
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new 分配的内存,一般速度比较慢,而且容易产生内存碎片, 不过用起来最方便。另外,在WINDOWS 下,最好的方式是用VirtualAlloc 分配内存,他不是在堆,也不是栈,而是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。
5 堆和栈中的存储内容
栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C 编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
6 存取效率的比较
char s1[] = \"a\";
char *s2 = \"b\";
a 是在运行时刻赋值的;而
b 是在编译时就确定的;但是,在以后的存取中,在栈上的数组比指针所指向的字符串( 例如堆) 快。比如:
int main(){
char a = 1;
char c[] = \"90\";
char *p =\"90\";
a = c[1];
return 0;
}
对应的汇编代码
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl 中,而第二种则要先把指针值读到edx 中,再根据edx 读取字符,显然慢了。
7 小结
堆和栈的主要区别由以下几点:
1 、管理方式不同;
2 、空间大小不同;
3 、能否产生碎片不同;
4 、生长方向不同;
5 、分配方式不同;
6 、分配效率不同;
管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak 。
空间大小:一般来讲在32 位系统下,堆内存可以达到4G 的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6 下面,默认的栈空间大小是1M 。当然,这个值可以修改。
碎片问题:对于堆来讲,频繁的new/delete 势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构。
生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。
分配方式:堆都是动态分配的,没有静态分配的堆。栈有2 种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由malloca 函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++ 函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/ 操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
从这里我们可以看到,堆和栈相比,由于大量new/delete 的使用,容易造成大量的内存碎
片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP 和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。
虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。
无论是堆还是栈,都要防止越界现象的发生(除非你是故意使其越界),因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生以想不到的结果。
4.new/delete 与malloc/free 比较
从C++ 角度上说,使用new 分配堆空间可以调用类的构造函数,而malloc() 函数仅仅是一个函数调用,它不会调用构造函数,它所接受的参数是一个unsigned long 类型。同样,delete 在释放堆空间之前会调用析构函数,而free 函数则不会。
class Time{
public:
Time(int,int,int,string);
~Time(){
cout<<\"call Time\'s destructor by:\"< } private: int hour; int min; int sec; string name; }; Time::Time(int h,int m,int s,string n){ hour=h; min=m; sec=s; name=n; cout<<\"call Time\'s constructor by:\"< } int main(){ Time *t1; t1=(Time*)malloc(sizeof(Time)); free(t1); Time *t2; t2=new Time(0,0,0,\"t2\"); delete t2; system(\"PAUSE\"); return EXIT_SUCCESS; } 结果: call Time\'s constructor by:t2 call Time\'s destructor by:t2 从结果可以看出,使用new/delete 可以调用对象的构造函数与析构函数,并且示例中调用的是一个非默认构造函数。但在堆上分配对象数组时,只能调用默认构造函数,不能调用其他任何构造函数 内存分配malloc,calloc,realloc realloc 原型:extern void *realloc(void *mem_address, unsigned int newsize); 用法:#include 功能:改变mem_address所指内存区域的大小为newsize长度。 说明:如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。 当内存不再使用时,应使用free()函数将内存块释放。 注意:这里原始内存中的数据还是保持不变的。 举例: // realloc.c #include #include main() { char *p; p=(char *)malloc(100); if(p) printf("Memory Allocated at: %x",p); else printf("Not Enough Memory!\n"); getchar(); p=(char *)realloc(p,256); if(p) printf("Memory Reallocated at: %x",p); else printf("Not Enough Memory!\n"); free(p); getchar(); return 0; } 详细说明及注意要点: 1、如果有足够空间用于扩大mem_address指向的内存块,则分配额外内存,并返回mem_address 这里说的是“扩大”,我们知道,realloc是从堆上分配内存的,当扩大一块内存空间时, realloc()试图直接从堆上现存的数据后面的那些字节中获得附加的字节,如果能够满足,自然天下太平。也就是说,如果原先的内存大小后面 还有足够的空闲空间用来分配,加上原来的空间大小= newsize。那么就ok。得到的是一块连续的内存。 2、如果原先的内存大小后面没有足够的空闲空间用来分配,那么从堆中另外找一块newsize大小的内存。 并把原来大小内存空间中的内容复制到newsize中。返回新的mem_address 指针。(数据被移动了)。 老块被放回堆上。 例如: #include #include main() { char *p,*q; p = (char * ) malloc (10); if(p) printf("Memory Allocated at: %x",p); else printf("Not Enough Memory!\n"); getchar(); q=p; p = (char * ) realloc (p,20000); if(p) printf("Memory Reallocated at: %x",p); else printf("Not Enough Memory!\n"); free(p); getchar(); printf("Memory Reallocated at: %x",p); printf("Memory Reallocated at: %x\n",q); return 0; } 在这段程序中我们增加了指针q,用它记录了原来的内存地址p。这段程序可以编译通过,但在执行到A行时,如果原有内存后面没有足够空间将原有空间扩展成一个连续的新大小的话,realloc函数就会以第二种方式分配内存,此时数据发生了移动,那么所记录的原来的内存地址q所指向的内存空间实际上已经放回到堆上了!这样就会产生q指针的指针悬挂,如果再用q指针进行操作就可能发生意想不到的问题。所以在应用realloc函数是应当格外注意这种情况。 从最后两个printf可以看出,free之后,p,q仍然指向原地址,而不是NULL,因为free表示要翻译p指向的内存空间。但不改变p的值。free(p)执行后,p指向的空间被释放了。但p本身的值并没有改变. 一般要求,指针被释放以后,要求写上p =NULL 3、返回情况 返回的是一个void类型的指针,调用成功。(这就再你需要的时候进行强制类型转换) 返回NULL,当需要扩展的大小(第二个参数)为0并且第一个参数不为NULL,此时原内存变成了“freed(游离)”的了。 返回NULL,当没有足够的空间可供扩展的时候,此时,原内存空间的大小维持不变。 #include #include #include main() { char *p,*q; p = (char * ) malloc (10); if(p) printf("Memory p Allocated at: %x",p); else printf("Not Enough Memory!\n"); getchar(); q=p; p = (char * ) realloc (p,pow(2,32));//注意不能写成2^32if(p) printf("Memory p Reallocated at: %x\n",p); else printf("Not Enough Memory!\n"); printf("Memory Reallocated at: %x\n",p); getchar(); free(p); printf("Memory p Reallocated at: %x\n",p); printf("Memory q Reallocated at: %x\n",q); return 0; } 4、特殊情况 如果mem_address为null,则realloc()和malloc()类似。分配一个newsize 的内存块,返回一个指向该内存块的指针。 如果newsize大小为0,那么释放mem_address指向的内存,并返回null。 如果没有足够可用的内存用来完成重新分配(扩大原来的内存块或者分配新的内存块),则返回null.而原来的内存块保持不变。 malloc 原型:extern void *malloc(unsigned int num_bytes); 用法:#include 或#include 功能:分配长度为num_bytes字节的内存块 说明:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。 当内存不再使用时,应使用free()函数将内存块释放。 malloc的语法是:指针名=(数据类型*)malloc(长度),(数据类型*)表示指针. 举例: // malloc.c #include #include main() { char *p; p=(char *)malloc(100); if(p) printf("Memory Allocated at: %x",p); else printf("Not Enough Memory!\n"); if(p) free(p); getchar(); return 0; } malloc()函数的工作机制 malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。浅析malloc()的几种实现方式 malloc()是C语言中动态存储管理的一组标准库函数之一。其作用是在内存的动态存储区中分配一个长度为size的连续空间。其参数是一个无符号整形数,返回值是一个指向所分配的连续存储域的起始地址的指针。 动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不像数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。本文简单介绍动态内存分配函数malloc()及几种实现方法。 1.简介 malloc()是C语言中动态存储管理的一组标准库函数之一。其作用是在内存的动态存储区中分配一个长度为size的连续空间。其参数是一个无符号整形数,返回值是一个指向所分配的连续存储域的起始地址的指针。还有一点必须注意的是,当函数未能成功分配存储空间(如内存不足)就会返回一个NULL指针。所以在调用该函数时应该检测返回值是否为NULL并执行相应的操作。 2.函数说明 C语言的动态存储管理由一组标准库函数实现,其原型在标准文件 注意,虽然这里的存储块是通过动态分配得到的,但是它的大小也是确定的,同样不允许越界使用。例如上面程序段分配的块里能存n个双精度数据,随后的使用就必须在这个范围内进行。越界使用动态分配的存储块,尤其是越界赋值,可能引起非常严重的后果,通常会破坏程序的运行系统,可能造成本程序或者整个计算机系统垮台。 下例是一个动态分配的例子: #include #include main() { int count,*array; count=10; if((array=(int *)malloc(count*sizeof(int))) == NULL) { printf("不能成功分配存储空间。"); exit(1); } for(count=0;count<10;count++) array[count]=count; for(count=0;count<10;count++) printf("%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 (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 void malloc_init() { last_valid_address = sbrk(0); managed_memory_start = last_valid_address; 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; mcb = firstbyte - sizeof(struct mem_control_block); mcb->is_available = 1; return; } 如您所见,在这个分配程序中,内存的释放使用了一个非常简单的机制,在固定时间内完成内存释放。分配内存稍微困难一些。以下是该算法的略述:清单 5. 主分配程序的伪代码 1. If our allocator has not been initialized, initialize it. 2. Add sizeof(struct mem_control_block) to the size requested. 3. start at managed_memory_start. 4. Are we at last_valid address? 5. If we are: A. We didn't find any existing space that was large enough -- ask the operating system for more and return that. 6. Otherwise: A. Is the current space available (check is_available from the mem_control_block)? B. If it is: i) Is it large enough (check "size" from the mem_control_block)? ii) If so: a. Mark it as unavailable b. Move past mem_control_block and return the pointer iii) Otherwise: a. Move forward "size" bytes b. Go back go step 4 C. Otherwise: i) Move forward "size" bytes ii) Go back to step 4 我们主要使用连接的指针遍历内存来寻找开放的内存块。这里是代码: 清单 6. 主分配程序 void *malloc(long numbytes) { void *current_location; struct mem_control_block *current_location_mcb; void *memory_location; if(! has_initialized) { malloc_init(); } numbytes = numbytes + sizeof(struct mem_control_block); memory_location = 0; current_location = managed_memory_start; while(current_location != last_valid_address) { current_location_mcb = (struct mem_control_block *)current_location; if(current_location_mcb->is_available) { if(current_location_mcb->size >= numbytes) { current_location_mcb->is_available = 0; memory_location = current_location; break; } } current_location = current_location + current_location_mcb->size; } if(! memory_location) { sbrk(numbytes); memory_location = last_valid_address; last_valid_address = last_valid_address + numbytes; current_location_mcb = memory_location; current_location_mcb->is_available = 0; current_location_mcb->size = numbytes; } memory_location = memory_location + sizeof(struct mem_control_block); return memory_location; } 这就是我们的内存管理器。现在,我们只需要构建它,并在程序中使用它即可。 5. malloc()的其他实现 malloc() 的实现有很多,这些实现各有优点与缺点。在设计一个分配程序时,要面临许多需要折衷的选择,其中包括: 分配的速度。 回收的速度。 有线程的环境的行为。 内存将要被用光时的行为。 局部缓存。 簿记(Bookkeeping)内存开销。 虚拟内存环境中的行为。 小的或者大的对象。 实时保证。 每一个实现都有其自身的优缺点集合。在我们的简单的分配程序中,分配非常慢,而回收非常快。另外,由于它在使用虚拟内存系统方面较差,所以它最适于处理大的对象。 还有其他许多分配程序可以使用。其中包括: Doug Lea Malloc:Doug Lea Malloc 实际上是完整的一组分配程序,其中包括 Doug Lea 的原始分配程序,GNU libc 分配程序和 ptmalloc。 Doug Lea 的分配程序有着与我们的版本非常类似的基本结构,但是它加入了索引,这使得搜索速度更快,并且可以将多个没有被使用的块组合为一个大的块。它还支持缓存,以便更快地再次使用最近释放的内存。 ptmalloc 是 Doug Lea Malloc 的一个扩展版本,支持多线程。在本文后面的参考资料部分中,有一篇描述 Doug Lea 的 Malloc 实现的文章。 BSD Malloc:BSD Malloc 是随 4.2 BSD 发行的实现,包含在 FreeBSD 之中,这个分配程序可以从预先确实大小的对象构成的池中分配对象。它有一些用于对象大小的 size 类,这些对象的大小为 2 的若干次幂减去某一常数。所以,如果您请求给定大小的一个对象,它就简单地分配一个与之匹配的 size 类。这样就提供了一个快速的实现,但是可能会浪费内存。在参考资料部分中,有一篇描述该实现的文章。 Hoard:编写 Hoard 的目标是使内存分配在多线程环境中进行得非常快。因此,它的构造以锁的使用为中心,从而使所有进程不必等待分配内存。它可以显著地加快那些进行很多分配和回收的多线程进程的速度。在参考资料部分中,有一篇描述该实现的文章。 众多可用的分配程序中最有名的就是上述这些分配程序。如果您的程序有特别的分配需求,那么您可能更愿意编写一个定制的能匹配您的程序内存分配方式的分配程序。不过,如果不熟悉分配程序的设计,那么定制分配程序通常会带来比它们解决的问题更多的问题。 6.结束语 前面已经提过,多次调用malloc()后空闲内存被切成很多的小内存片段,这就使得用户在申请内存使用时,由于找不到足够大的内存空间,malloc()需要进行内存整理,使得函数的性能越来越低。聪明的程序员通过总是分配大小为2的幂的内存块,而最大限度地降低潜在的malloc性能丧失。也就是说,所分配的内存块大小为4字节、8字节、16字节、0616字节,等等。这样做最大限度地减少了进入空闲链的怪异片段(各种尺寸的小片段都有)的数量。尽管看起来这好像浪费了空间,但也容易看出浪费的空间永远不会超过50%。