malloc()函数

malloc的全称是memory allocation,中文叫动态内存分配。

extern void *malloc(unsigned int num_bytes);

说明:

分配长度为num_bytes字节的内存块。如果分配成功则返回指向被分配内存的指针,分配失败返回空指针NULL。当内存不再使用时,应使用free()函数将内存块释放。

void *malloc(int size);

说明:

malloc 向系统申请分配指定size个字节的内存空间,返回类型是 void 类型。void 表示未确定类型的指针。C,C++规定,void* 类型可以强制转换为任何其它类型的指针。   

备注:void* 表示未确定类型的指针,更明确的说是指申请内存空间时还不知道用户是用这段空间来存储什么类型的数据(比如是char还是int或者…)

注意事项

  1. 申请了内存空间后,必须检查是否分配成功。

  2. 当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。

  3. malloc和free应该是配对。如果申请后不释放就是内存泄露;如果无故释放那就是什么也没有做。释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。

虽然malloc()函数的类型是(void *),任何类型的指针都可以转换成(void *),但是最好还是在前面进行强制类型转换,因为这样可以躲过一些编译器的检查。

free函数

free释放指针指向的内存空间

void free(void *FirstByte)

该函数是将之前用malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存,让它重新得到自由。

注意ptr只能指向可用空间的首地址,然后全部释放申请内存,而不能部分释放。注意因为malloc实现了内存池,所以free掉的内存不一定会马上被释放。

大多数实现所分配的存储空间比所要求的要稍大一些,额外的空间用来记录管理信息——分配块的长度,指向下一个分配块的指针等等。这就意味着如果写过一个已分配区的尾端,则会改写后一块的管理信息。这种类型的错误是灾难性的,但是因为这种错误不会很快就暴露出来,所以也就很难发现。将指向分配块的指针向后移动也可能会改写本块的管理信息。

在对内存块进行了 free 调用之后,我们需要做的是诸如将它们标记为未被使用的等事情,并且,在调用 malloc 时,我们要能够定位未被使用的内存块。因此, malloc返回的每块内存的起始处首先要有这个结构:

//清单 3. 内存控制块结构定义
struct mem_control_block {
    int is_available;
    int size;
};

现在,您可能会认为当程序调用 malloc 时这会引发问题 —— 它们如何知道这个结构?答案是它们不必知道;在返回指针之前,我们会将其移动到这个结构之后,把它隐藏起来。这使得返回的指针指向没有用于任何其他用途的内存。那样,从调用程序的角度来看,它们所得到的全部是空闲的、开放的内存。然后,当通过 free() 将该指针传递回来时,我们只需要倒退几个内存字节就可以再次找到这个结构。

为了释放内存,我们必须要做的惟一一件事情就是,获得我们给出的指针,回退 sizeof(struct mem_control_block) 个字节,并将其标记为可用的。这里是对应的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//清单 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;
}

如您所见,在这个分配程序中,内存的释放使用了一个非常简单的机制,在固定时间内完成内存释放。

malloc和free底层实现(内存池)

malloc底层内存的分配和STL中的空间配置器很类似:

  1. 当开辟的空间小于128k时,调用brk()函数,malloc的底层实现是系统调用函数brk(),其主要移动指针_enddata来开辟空间、

  2. 当开辟的空间大于128k时,mmap()系统调用函数来在虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空间来开辟。

free底层实现:

设系统调用brk先分配了内存A,然后在分配内存B,系统调用mmap分配了内存C,

  1. 如果free由mmap分配的内存C,直接将C中的虚拟内存和物理内存释放回给操作系统

  2. 如果free有brk分配的A,A的虚拟内存和物理内存都没有释放,但是A的内存可以重用

  3. 如果再释放brk分配的B,如果A和B的内存数目大于128k则将A和B的虚拟内存和物理内存都释放回给操作系统,否则也没有释放

在标准C库中,提供了malloc/free函数分配释放内存,这两个函数底层是由brk,mmap,munmap这些系统调用实现的。

为什么malloc在小于128k时调用brk,而大于128k时调用mmap呢,为什么不全用brk或全用mmap呢 ?

主要是为了较少内存碎片。

brk分配的内存只有等高地址的内存释放了低地址的内存才会释放,如果全是用brk,那么会因为内存的释放而导致产生很多内存碎片

mmap分配的内存是随机分配的,可能不连续,如果全用mmap的分配内存的话,会因为内存的不连续而产生很多内存碎片

所以使用一个折中的方法,小于128k使用brk,大于128k使用mmap

情况一

malloc小于128k的内存,使用brk分配内存,将_edata往高地址推(只分配虚拟空间,不对应物理内存(因此没有初始化),第一次读/写数据时,引起内核缺页中断,内核才分配对应的物理内存,然后虚拟地址空间建立映射关 系),如下图:

  1. 进程启动的时候,其(虚拟)内存空间的初始布局如图1所示。

    其中,mmap内存映射文件是在堆和栈的中间(例如libc-2.2.93.so,其它数据文件等),为了简单起见,省略了内存映射文件。

    _edata指针(glibc里面定义)指向数据段的最高地址。

  2. 进程调用A=malloc(30K)以后,内存空间如图2

    malloc函数会调用brk系统调用,将_edata指针往高地址推30K,就完成虚拟内存分配。

    你可能会问:只要把_edata+30K就完成内存分配了?

    事实是这样的,_edata+30K只是完成虚拟地址的分配,A这块内存现在还是没有物理页与之对应的,等到进程第一次读写A这块内存的时候,发生缺页中断,这个时候,内核才分配A这块内存对应的物理页。也就是说,如果用malloc分配了A这块内容,然后从来不访问它,那么,A对应的物理页是不会被分配的。

    因为申请的内存不一定马上使用,推迟分配可以系统拥有更多的空闲物理内存去出来其他事,从而提高系统的吞吐量

  3. 进程调用B=malloc(40K)以后,内存空间如图3。

情况二

malloc大于128k的内存,使用mmap分配内存,在堆和栈之间找一块空闲内存分配(对应独立内存,而且初始化为0),如下图:

  1. 进程调用C=malloc(200K)以后,内存空间如图4:

    默认情况下,malloc函数分配内存,如果请求内存大于128K(可由M_MMAP_THRESHOLD选项调节),那就不是去推_edata指针了,而是利用mmap系统调用,从堆和栈的中间分配一块虚拟内存。

    这样子做主要是因为:brk分配的内存需要等到高地址内存释放以后才能释放,而mmap分配的内存可以单独释放。

  2. 进程调用D=malloc(100K)以后,内存空间如图5;

  3. 进程调用free(C)以后,C对应的虚拟内存和物理内存一起释放。

  1. 进程调用free(B)以后,如图7所示:

    B对应的虚拟内存和物理内存都没有释放,因为只有一个_edata指针,如果往回推,那么D这块内存怎么办呢?

    当然,B这块内存,是可以重用的,如果这个时候再来一个40K的请求,那么malloc很可能就把B这块内存返回回去了。

  2. 进程调用free(D)以后,如图8所示:

    B和D连接起来,变成一块140K的空闲内存。

    默认情况下:

    当最高地址空间的空闲内存超过128K(可由M_TRIM_THRESHOLD选项调节)时,执行内存紧缩操作(trim)。在上一个步骤free的时候,发现最高地址空闲内存超过128K,于是内存紧缩,变成图9所示。

calloc函数

void *calloc(unsigned n,unsigned size);

在内存的动态存储区中分配n个长度为size的连续空间,函数返回一个指向分配起始地址的指针;如果分配不成功,返回NULL。

跟malloc的区别:calloc在动态分配完内存后,自动初始化该内存空间为零,而malloc不初始化,里边数据是随机的垃圾数据。

realloc函数

extern void *realloc(void *mem_address, unsigned int newsize);

先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_address返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address所指内存区域,同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。

参考: http://blog.csdn.net/maybe3is3u5/article/details/51984744 http://blog.csdn.net/eroswang/article/details/4265024