malloc函数的底层实现你是否清楚

说起malloc函数,每个人都能说出它的功能,而且我们经常会用到,那么今天我要说的是关于malloc函数在编译器的底层实现,如果你对它的实现已经很清楚了,那么你可以不往下看了,因为这篇博客只是就它的一些简单原理进行了整理,你可以等我的下一篇博客,对它的深层的一些函数在进行的一些讲述。
      这篇博客对于深层的函数实现并没有解释,只是让我们明白了windows系统中的一些分配算法的原理。请读者多多指正,因为在空闲链表分配算法上我看到过不同的说法。

关于VirtualAlloc函数:

首先我们来看一下VirtualAlloc函数,它是WINDOWS系统提供给我们的一个API,它的功能就是向操作系统给我们批发内存。为什么叫批发呢,因为在操作系统中并不是我们所编写的应用程序直接向系统申请内存,在VirtualAlloc函数向内存申请内存的时候是申请内存的大小必须是4096字节的整数倍,这就相当于是一个水果贩子去大批发市场进货,人家批发市场是有起发的要求的,不是说你去买上几斤都行,所以我们都是从水果贩子的手上再去买水果。这样是有道理的,因为你一个程序假如只需要申请几字节的内存,那么系统给我们4096字节的大小是不是很浪费。

那么用VirtualAlloc申请的内存又是怎么拿给我们的程序来用的呢,在这里就牵扯到了分配算法。其实在windows中在对管理器上提供了几个函数用来创建、分配、释放和销毁堆空间(我们申请的空间是在堆上的),从而实现了分配算法。

HeapCreat:创建一个堆
HeapAlloc:在堆里分配空间
HeapFree:释放分配的内存
HeapDestroy:销毁一个堆

这里的HeapCreat便是创建一个堆空间它申请内存空间便是通过VirtualAlloc函数来实现的。HeapAlloc函数是在堆空间里面给用户分配小的内存空间,如果内存不足它也能通过VirtualAlloc向系统申请更多的内存。

Malloc函数:

说了前面那么多,终于说道我们的重点了,malloc函数其实是上面几个Heap操作函数的包装。
     堆空间是程序申请出来的一大块空间,当我们用malloc函数申请空间的时候大小是不确定的,有可能是很小的一块空间也有可能是很大的一块空间,所以对堆空间也是需要一定的方法进行管理的,当你需要申请空间的时候按照你申请的大小以一定方法分配给你。
     这就是分配算法,分配算法有很多种,对于不同的场合是有不同的分配算法的。在这里只简单的描述一下几种简单的算法。

1. 空闲链表

我们知道在一块堆空间中,能用的空间到时候是一块一块的分布着,空闲链表的方法就是把这些空闲的块用链表的方式进行连接,如果用户申请一块空间,就可以遍历这个空闲链表找到能够容纳你申请的大小的一个空闲块,然后拆分,把合适大小的一块返回给你;当用户释放一块空间时,在把这块空间链接在空闲链表上。下面来看一下它的结构:

这里画的是其中的一块,在这块空间的前面存放了两个指针,N代表next指针用来指向它的下一块空间,如果下一块为空,则指针指向NULL,P代表Prev指针,用来指向它的前一块空间,如果为NULL代表他就是空闲链表的第一块空间。

这里给出了一个只有三个块的空闲链表,它们的指针指向就是如图所指那样,此时我的块都是空闲的。假如我现在要申请一块空间,它是怎么实现的呢?

假设我要申请的空间大小刚好是第二块空间的大小,那么会把第二块空间的地址返回给我们,然后把这块空间从原来的空闲链表上删掉。此时链表如下图所示:

其实一般找到的空间都是比我们所申请的空间大的,然后把这块空间进行了拆分,一部分就是就是我们所申请的空间,另一部分为剩下的空间,它还是对应在原来的空心链表上。
     这样就实现了一种简单的分配算法,其实在释放这块空间的时候,虽然知道指向这块空间的指针,但是堆并不知道这块空间的大小,那么它就不知道该释放多大的一块空间。所以其实在我们刚才说的分配算法里,在给用户分配空间的时候会多分配4个字节,作用就是用来存放这块空间的大小,这样的话在释放的时候找到这一块空间,就知道这块空间到底有多大,然后进行释放。

但是,我们知道有内存越界这种情况,就是在我们用内存的时候不小心用了它后面不属于它的空间,那么像空闲链表这种结构,可能就会把那块地方存放的两个指针进行了修改,这样不就破坏了我们的链表,然后整个空间就不能再使用了,这就是这种结构的缺点。

2. 位图

还有一种分配方式是位图,它是把我们的一块堆空间进行划分,划分成大小形同的一些块,当用户申请空间时,它会给我们分配整数个块以至于能存放你所申请的大小,第一个块是头,接下来的块都是用户申请的空间的主体,这样的话在位图中,我们的每一个快都有可能且只有可能有三种情况,就是头(Head)/主体(Body)/空闲(Free)。
      下图就是一个例子,在下图中分配了两片内存,第一片占用了四个块,第二片占用的五个块。

这样做的话到底有什么好处呢,首先这样分配的话,对于这个堆的存储信息,它是记录在一个数组里。因为每一个小块是有三种可能状态,那么用二进制的两个位就能够表示了,假如设为00位空闲,10位主体,11为头。这样整个位图在一个数组中就能表示了。
      这样每一个堆空间都可以用N个字节来表示,比如堆的大小为1Mb,分成1M/128=8000个块(每个块设为128字节),我们知道一个int是4个字节32位,那么能用8000/(32/2)=512个int来存储,所以一个大小为512int的数组就是一个完整的位图。

位图有它的优点:

比空闲链表更加稳定,因为可以对数组进行备份,而且就算某个块损坏,也不会影响整个位图其他的块空间。
     速度比较快还容易管理。

同时也有它的缺点:

也容易造成块的浪费,因为毕竟它是整数倍的分配。
     当堆比较大的时候,可能这个位图会很大,数组很庞大,可能效率也并不像想象那么快。

3. 对象池

还有一种方法是对象池,也是把堆空间分成了大小相等的一些块,它是认为某些场合每次分配的空间都相等,所以每次就直接返回一个块的大小,它的管理方法可以是链表也可以是位图。因为不用每次查找合适的大小的内存返回,所以效率很高。     

      其实在实际的应用中,堆的分配算法有很多,上面的三种只是其中简单的几种,而且在实际分配中它是根据应用场景可能对应不同的分配算法,也可能是多种算法的结合。只有这样才会达到高效的这么一个原则。

malloc函数的底层实现你是否清楚的更多相关文章

  1. malloc 函数工作机制(转)

    malloc()工作机制 malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表.调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块.然后,将 ...

  2. C语言 malloc函数

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.                                                 ...

  3. 关于malloc函数的动态分配问题

    malloc函数动态分配了一个整型的内存空间,让abc都指向刚申请的空间,所以只有最后一个赋值语句的值保留在了空间里 #include<stdio.h> main() { int *a,* ...

  4. malloc函数详解

    一.原型:extern void *malloc(unsigned int num_bytes); 头文件:#include <malloc.h> 或 #include <alloc ...

  5. malloc 函数到底做了什么?

    请看下面的代码. 猜测结果是什么?编译通过吗? #include <stdio.h> #include <stdlib.h> int main() { ; char *ptr ...

  6. malloc函数和其他内存分配函数

    1. 需要包含头文件:#i nclude 或 #i nclude 函数声明(函数原型): void *malloc(int size); 说明:malloc 向系统申请分配指定size个字节的内存空间 ...

  7. malloc函数

    C语言中,使用malloc函数向内存中动态申请空间. 函数的原型是extern void *malloc(unsigned int num_bytes); 可见,函数返回的是指针类型,参数是要申请的空 ...

  8. 如何实现一个malloc函数

    一.概述 1.malloc简介 函数所在头文件:<stdlib.h> 函数原型是:void *malloc (size_t n) 函数功能:在内存的动态存储区中分配一个长度为size的连续 ...

  9. C语言malloc()函数:动态分配内存空间

    头文件:#include <stdlib.h> malloc() 函数用来动态地分配内存空间(如果你不了解动态内存分配,请查看:C语言动态内存分配及变量存储类别),其原型为:void* m ...

随机推荐

  1. Eclipse导入的工程后referenced libraries中的jar包中文注释显示乱码解决方法

    Preferences-General-Workspace-Text file encoding 设置为uft-8 最后重启一下eclipse.

  2. 2015年10月23日JS笔记

    ECMAScript标准:JavaScript核心语法 微软:Jscript ECMAScript标准:一纸空文 JavaScript和JScritp都号称完全实现了 ECMAScript标准 W3C ...

  3. Linux Vi 删除全部内容,删除某行到结尾,删除某段内容 的方法

    1.打开文件 vi filename 2.转到文件结尾 G 或转到第9行 9G 3.删除所有内容(先用G转到文件尾) ,使用: :1,.d 或者删除第9行到第200行的内容(先用200G转到第200行 ...

  4. codeforce 606C - Sorting Railway Cars

    题意:给你一串数,没个数只能往前提到首位,或则往后放末尾.问最少步骤使操作后的序列成上升序列. 思路:最长连续子序列. #include<iostream> #include<std ...

  5. openstack python sdk list tenants get token get servers

    1,openstack python sdk 获取token 获取租户tenants projects #!/bin/bash export OS_PROJECT_DOMAIN_ID=default ...

  6. iOS7滑动返回

    [转载请注明出处] iOS 7中在传统的左上角返回键之外,提供了右滑返回上一级界面的手势.支持此手势的是UINavigationController中新增的属性 interactivePopGestu ...

  7. Modbus Poll master-slave测试 Dtech USB转485(worldsing 笔记)

    1,简介 网站地址:http://www.modbustools.com/ 该网站提供了几个软件工具,可以运行于windows 2000/XP/Vista/7环境下,用来测试和仿真Modebus设备. ...

  8. SQL Server Profiler参数说明

    上图依次说明为: TextDate 依赖于跟踪中捕获的事件类的文本值: ApplicationName 创建 SQL Server 连接的客户端应用程序的名称.此列由该应用程序传递的值填充,而不是由所 ...

  9. Android 解析 xml

    URL httpUrl = new URL(url); HttpURLConnection conn = (HttpURLConnection )httpUrl.openConnection(); c ...

  10. ecshop后台admin文件夹任意更改名

    为了ecshop网站安全起见或不想泄露后台的路径,那么我们必须修改后台admin文件夹名称. 方法和步骤如下: 把原admin文件夹名改成edait为例来说明 首先,把商城根目录下的admin文件夹重 ...