malloc()工作机制
malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。 但如果合并之后还是不够申请大小呢,怎么办?此时分配器会调用sbrk函数,向内核请求额外的堆存储器,分配器将额外的存储器转换为一个大的空闲块,然后将这个块插入到空闲链表中,然后将被请求的块放置在这个新的空闲块中。
---------------------------------------------------------------------------------------------------------
(可参考:
/thread-2195511-1-1.html
)
图1
图2
------------------------------------------------------LINUX---------------------------------------------------
----------------------------------------------------WINDOWS-------------------------------------------------
(可参考
/bbs/read.?tid-6357.html
)
C 语言标准定义了一系列的内存管理函数,如 malloc(), memcpy(), free() 等。Windows 系统也支持标准 C 。在应用程序中,也可以使用标准 C 内存管理函数来对内存分配释放,操作管理。
ctrdll.dll (crt : c run time) 中包括了各种标准 C 库函数的实现,使用标准 C 库函数并不会脱离 Windows 系统内存管理模式。事实上,在 Windows 上,和大多数标准 C 库函数一样,像 malloc() 这种函数仍然是通过 API 函数实现的。malloc() 在对其参数进行了简单的处理后就直接调用了 函数。调用关系为 : new -> malloc -> HeapAlloc -> VirtualAlloc -> 驱动程序的_PageAlloc
堆内存管理依赖于虚拟内存管理。在创建堆时,HeapCreate() 函数会向系统请求虚拟内存分页,然后在这个堆上的内存分配实际上是从虚拟内存管理中获取的内存分页上再分配大小任意的内存块。如果在建立堆时指定了堆的大小是固定的,那么在堆上分配内存块时,其范围不能超过设置的堆大小,堆大小一定是内存页大小的整数倍。如果在建立堆时不固定堆的大小,那堆管理函数会根据分配的请求数量,动态地向虚拟内存管理函数请求内存分页。 的功能是对进程虚拟地址空间中内存分页的状态进行管理,属于虚拟内存管理(以内存页为单位)的范围。当然,内存的分配也是通过改变虚拟内存内存页面属性来实现的,所以 间接达到了分配内存的目的。堆管理器也是依赖于虚拟内存管理的,在收到 的内存分配的请求时,堆管理器会根据情况决定是否使用虚拟内存管理机制分配新的页面,以及如何在页面上分配内存块,因此从这个层次上来讲,VirtualAlloc() 更为底层。实际上,只要虚拟内存管理函数将页面属性设置为 “已提交”后,就可以在上面进行读写等操作。堆管理函数为应用程序提供了一种更灵活,更简单的方式来管理这些“已提交”的内存,其好处一就是分配更简单,二是可以分配任意大小的内存,三是不用直接与复制的内存分页机制打交道。如标准 C 中的 malloc() 函数实际上就是直接调用了 ,因此其效率也会比直接使用 HeapAlloc() 来得低。因为 HeapAlloc() 和 VirtualAlloc() 的层次和功能定位都不同,因此在效率上没有可比性。从原理上来讲,HeapAlloc() 在分配内存时,如果堆管理器中有足够的已提交的页面可用,那么它就不需要将内存分页从其他状态(空闲或保留的)改为已提交状态,只需要在堆管理器中进行相关管理即可。如果在分配时没有足够的已提交页面可供使用,那么还需要将虚拟内存分页从其他状态改为已提交状态,在有足够的可使用已提交页面时,再由对管理器进行相关管理。
虚拟地址空间内存页有 3 种状态,它们分别是:空闲的(free),保留的(reserved),提交的(committed) 。大多数情况下,一个页的大小是 4KB 。
空闲的对于空闲页面,由于页面还未被分配,因此进程不能访问。对这属于种页面的虚拟内存地址访问都将引起访问异常。保留的被保留的页面为将来之备用。这些页面已经分配,但是还没被使用,物理地址空间中的内存不存在与其对应的物理内存分页。处理保留状态的内存分页也不能被访问。提交的内存已经被分配,并且已经被使用,具有与之对应的物理地址空间中的内存分页。
----------------------------------------------------WINDOWS-------------------------------------------------
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%。
---------------------------------------------------------------------------------------------
malloc函数
函数声明(函数原型): void *malloc(int size); 说明:malloc 向系统申请分配指定size个字节的内存空间。返回类型是 void* 类型。void* 表示未确定类型的指针。C,C++规定,void* 类型可以强制转换为任何其它类型的指针。 从函数声明上可以看出。malloc 和 new 至少有两个不同: new 返回指定类型的指针,并且可以自动计算所需要大小。比如: int *p; p = new int; //返回类型为int* 类型(整数型指针),分配大小为 sizeof(int); 或: int* parr; parr = new int [100]; //返回类型为 int* 类型(整数型指针),分配大小为 sizeof(int) * 100; 而 malloc 则必须由我们计算要字节数,并且在返回后强行转换为实际类型的指针。 int* p; p = (int *) malloc (sizeof(int)); 第一、malloc 函数返回的是 void * 类型,如果你写成:p = malloc (sizeof(int)); 则程序无法通过编译,报错:“不能将 void* 赋值给 int * 类型变量”。所以必须通过 (int *) 来将强制转换。 第二、函数的实参为 sizeof(int) ,用于指明一个整型数据需要的大小。如果你写成: int* p = (int *) malloc (1); 代码也能通过编译,但事实上只分配了1个字节大小的内存空间,当你往里头存入一个整数,就会有3个字节无家可归,而直接“住进邻居家”!造成的结果是后面的内存中原有数据内容全部被清空。 malloc 也可以达到 new [] 的效果,申请出一段连续的内存,方法无非是指定你所需要内存大小。 比如想分配100个int类型的空间: int* p = (int *) malloc ( sizeof(int) * 100 ); //分配可以放得下100个整数的内存空间。 另外有一点不能直接看出的区别是,malloc 只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的。 除了分配及最后释放的方法不一样以外,通过malloc或new得到指针,在其它操作上保持一致。
--------------------------------------------------------------------------------------------
有了malloc/free 为什么还要new/delete ?
malloc 与free 是C++/C 语言的标准库函数,new/delete 是C++的运算符。它们都可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,光用maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数, 对象在消亡之前要自动执行析构函数。由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete 不是库函数。我们先看一看malloc/free 和new/delete 如何实现对象的动态内存管理,见示例7-8。class Obj{ public :Obj(void){ cout << “Initialization” << endl; }~Obj(void){ cout << “Destroy” << endl; }void Initialize(void){ cout << “Initialization” << endl; }void Destroy(void){ cout << “Destroy” << endl; }};void UseMallocFree(void){ Obj *a = (obj *)malloc(sizeof(obj)); // 申请动态内存a->Initialize(); // 初始化//…a->Destroy(); // 清除工作free(a); // 释放内存}void UseNewDelete(void){ Obj *a = new Obj; // 申请动态内存并且初始化//…delete a; // 清除并且释放内存}示例7-8 用malloc/free 和new/delete 如何实现对象的动态内存管理类Obj 的函数Initialize 模拟了构造函数的功能,函数Destroy 模拟了析构函数的功能。函数UseMallocFree 中,由于malloc/free 不能执行构造函数与析构函数,必须调用成员函数Initialize 和Destroy 来完成初始化与清除工作。函数UseNewDelete 则简单得 多。 所以我们不要企图用malloc/free 来完成动态对象的内存管理,应该用new/delete。 由于内部数据类型的“ 对象”没有构造与析构的过程,对它们而言malloc/free 和 new/delete 是等价的。 既然new/delete 的功能完全覆盖了malloc/free,为什么C++不把malloc/free 淘 汰出局呢?这是因为C++程序经常要调用C 函数,而C 程序只能用malloc/free 管理动 态内存。 如果用free 释放“new 创建的动态对象”,那么该对象因无法执行析构函数而可能 导致程序出错。如果用delete 释放“malloc 申请的动态内存”,理论上讲程序不会出错, 但是该程序的可读性很差。所以new/delete 必须配对使用,malloc/free 也一样。