C语言动态内存分配:(一)malloc/free的实现及malloc实际分配/释放的内存

1、malloc/free概述安全

malloc是在C语言中用于在程序运行时在堆中进行动态内存分配的库函数。free是进行内存释放的库函数。函数

一、函数原型编码

#include <stdlib.h>
 
void *malloc( 
   size_t size 
);
void free( 
   void* memblock 
);
二、返回值
成功时,返回所分配存储空间的起始地址;返回值类型为void*,在C语言中能够把void*直接付给具体的类型,可是在C++中必须进行强制类型转换spa

失败时(内存不足时)返回NULL操作系统

size为0时,返回的指针不是NULL;可是除了free,别的地方不要使用这个指针。.net

三、使用示例设计

#include <stdlib.h>         /* For _MAX_PATH definition */
#include <stdio.h>
#include <malloc.h>
 
void main( void )
{
   char *string;
 
   /* Allocate space for a path name */
   string = malloc( _MAX_PATH );
 
   // In a C++ file, explicitly cast malloc's return.  For example, 
   // string = (char *)malloc( _MAX_PATH );
 
   if( string == NULL )
      printf( "Insufficient memory available\n" );
   else
   {
      printf( "Memory space allocated for path name\n" );
      free( string );
      printf( "Memory freed\n" );
   }
}指针

2、malloc实际分配的内存大小blog

   malloc实际分配的内存会大于咱们须要的size。主要由两方面因素决定:内存

一、字节对齐。会对齐到机器最受限的类型(具体的实现因机器而异)。

二、“块头部信息”。每一个空闲块都有“头部”控制信息,其中包含一个指向链表中下一个块的指针、当前块的大小和一个指向自己的指针。为了简化块对齐,全部块的大小都必须是头部大小的整数倍,且头部已正确对齐。

在VC平台下由_CrtMemBlockHeader结构体实现。

如下为《C程序设计语言》中给出的经过union进行的头部实现,其中假定机器的受限类型为long。

typedef long Align;/*按照long类型的边界对齐*/
union header/*块的头部*/
{
    struct
    {
        union header *ptr;/*空闲块链表中的下一块*/
        unsigned size;/*本块的大小*/
    }s;
    Align x;/*强制块对齐*/
};
说明:
(1)实际分配的内存块将多一个单元,用于头部自己。实际分配的块的大小被记录在头部的size字段中。
(2)size字段是必须的,由于malloc函数控制的块不必定是连续的,这样就不能经过指针算术运算计算其大小。

(3)malloc返回的是空闲块的首地址,而不是首地址。

3、malloc/free实现过程

   一、空闲存储空间以空闲链表的方式组织(地址递增),每一个块包含一个长度、一个指向下一块的指针以及一个指向自身存储空间的指针。( 由于程序中的某些地方可能不经过malloc调用申请,所以malloc管理的空间不必定连续。)

  二、当有申请请求时,malloc会扫描空闲链表,直到找到一个足够大的块为止(首次适应)(所以每次调用malloc时并非花费了彻底相同的时间)。

  三、若是该块刚好与请求的大小相符,则将其从链表中移走并返回给用户。若是该块太大,则将其分为两部分,尾部的部分分给用户,剩下的部分留在空闲链表中(更改头部信息)。所以malloc分配的是一块连续的内存。

  四、释放时,首先搜索空闲链表,找到能够插入被释放块的合适位置。若是与被释放块相邻的任一边是一个空闲块,则将这两个块合为一个更大的块,以减小内存碎片。

4、实现

如下为《C语言程序设计语言》中给出的一种实现方法

一、malloc的实现

typedef union header Header;
static Header base;/*从空链表开始*/
static Header *freep = NULL;/*空闲链表的初始指针*/
 
void *malloc(unsigned nbytes)
{
    Header *p, *prevp;
    Header *morecore(unsigned);
    unsigned nunits;
 
    nunits = (nbytes+sizeof(Header)-1)/sizeof(Header) + 1;
    if((prevp = freep) == NULL) /* 没有空闲链表 */
    { 
        base.s.ptr = freep = prevp = &base;
        base.s.size = 0;
    }
    for(p = prevp->s.ptr; ;prevp = p, p= p->s.ptr) 
    {
        if(p->s.size >= nunits) /* 足够大 */
        { 
            if (p->s.size == nunits)  /* 正好 */
                prevp->s.ptr = p->s.ptr;
            else  /*分配末尾部分*/
            {                 
                p->s.size -= nunits;
                p += p->s.size;
                p->s.size = nunits;
            }
            freep = prevp;
            return (void*)(p+1);
        }
        if (p== freep) /* 闭环的空闲链表*/
            if ((p = morecore(nunits)) == NULL)
                return NULL; /* 没有剩余的存储空间 */
    }
}
说明:

(1)malloc实际分配的空间是Header大小的整数倍,而且多出一个Header空间用于放置Header

(2)式nunits = (nbytes+sizeof(Header)-1)/sizeof(Header) + 1中的减1是为了防止(nbytes+sizeof(Header))%sizeof(Header) == 0时,多分配了一个Header大小的空间

(3)第一次调用malloc函数时,freep为NULL,系统将建立一个退化的空闲链表,它只包含一个大小为0的块,且该块指向本身。任什么时候候,当请求空闲空间时,都将搜索空闲块链表。搜索从上一次找到空闲块的地方(freep)开始。该策略能够保证链表是均匀的。若是找到的块太大,则将其尾部返回给用户,这样,初始块的头部只须要修改size字段便可。

(4)任什么时候候,返回给用户的指针都指向块内的空闲存储空间,即比指向头部的指针大一个单元。

(5)sbrk不是系统调用,是C库函数。sbrk/brk是从堆中分配空间,本质是移动一个位置,向后移就是分配空间,向前移就是释放空间,sbrk用相对的整数值肯定位置,若是这个整数是正数,会从当前位置向后移若干字节,若是为负数就向前若干字节。在任何状况下,返回值永远是移动以前的位置。在LINUX中sbrk(0)能返回比较精确的虚拟内存使用状况。连接:sbrk()/brk()--改变数据长度

(6)连接:brk()分配过程

二、morecore的实现

    函数morecore用来向操做系统请求存储空间,其实现细节因系统的不一样而不一样。由于向系统请求存储空间是一个开销很大的操做,所以咱们不但愿每次调用malloc时都执行该操做,基于这个考虑,morecore函数请求至少NALLOC个单元。这个较大的块将根据须要分红较小的块。在设置完size字段后,morecore函数调用free函数把多余的存储空间插入到空闲区域中。

#define NALLOC 1024    /* 最小申请单元数 */
static Header *morecore(unsigned nu)
{
    char *cp;
    Header *up;
    if(nu < NALLOC)
        nu = NALLOC;
    cp = sbrk(nu * sizeof(Header));
    if(cp == (char *)-1)    /* 没有空间*/
        return NULL;
    up = (Header *)cp;
    up->s.size = nu;
    free((void *)(up+1));
    return freep;
}
说明:

(1)没有存储空间时sbrk调用返回-1.所以须要将-1强制类型转换为char*类型,以便 与返回值进行比较。并且,强制类型转换使得函数不会受不一样机器中指针表示的不一样的影响。

三、free的实现

     free函数从freep指向的地址开始,逐个扫描空闲链表,寻找能够插入空闲块的地方。该位置可能在链表的末尾或者两个空闲块之间。在任何一种状况下,若是被释放的块与另外一空闲块相邻,则将这两个块合并。

void free(void *ap)
{
    Header *bp,*p;
    bp = (Header *)ap -1; /* 指向块头 */
    for(p=freep;!(bp>p && bp< p->s.ptr);p=p->s.ptr)
        if(p>=p->s.ptr && (bp>p || bp<p->s.ptr))
            break;    /* 被释放的块在链表的开头或结尾*/
    if (bp+bp->s.size==p->s.ptr) /*与上一相邻块合并 */
    {    
        bp->s.size += p->s.ptr->s.size;
        bp->s.ptr = p->s.ptr->s.ptr;
    } 
    else
        bp->s.ptr = p->s.ptr;
    if (p+p->s.size == bp)/* 与下一相邻块合并 */ 
    {     
        p->s.size += bp->s.size;
        p->s.ptr = bp->s.ptr;
    } 
    else
        p->s.ptr = bp;
    freep = p;
}
5、注意事项

连接:malloc/free使用注意事项

参考文献:

一、《C程序设计语言》

二、《C和指针》

三、《C和C++安全编码》

四、连接:如何实现一个malloc

———————————————— 版权声明:本文为CSDN博主「Z-H-I」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处连接及本声明。 原文连接:https://blog.csdn.net/zxx910509/article/details/62881131

相关文章
相关标签/搜索