C语言的stdlib库提供了内存分配与管理的函数:

1、通过调用calloc、malloc和realloc所分配的空间,如果连续调用它们,不能保证空间是顺利或连续的。当分配成功后,函数将返回一个指针,这个指针指向分配成功的空间的开始位置, 它可以被指向任意类型的对象,当分配空间失败后,返回NULL指针。

2、通过free函数释放空间。

3、函数说明

(1)calloc函数

函数的原型为:

void *calloc(size_t nmemb,size_t size);

为nmemb个对象的数组分配空间,每个元素的大小为size,分配的空间所有位被初始为0。

(2)free函数

函数的原型为:

原型:extern void free(void *ptr);

释放指针ptr所指向的的内存空间。ptr所指向的内存空间必须是用calloc,malloc,realloc所分配的内存。如果ptr为NULL或指向不存在的内存块则不做任何操作。

释放的空间还可以被重新分配,

(3)malloc函数

void *malloc(size_t size);

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

(4)realloc函数

void realloc(void ptr, size_t size);

改变ptr所指内存区域的大小为size长度。如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。当内存不再使用时,应使用free()函数将内存块释放。新分配空间比空间大,并包括原空间的的内容,但因为分配新空间,没有初始化0的操作,所以新空间中除去旧空间的部分的内容不能保证清空为零。

4、C语言中数据对象空间分配原理
C语言中的数据对象存储在以下3种空间中:

(1) 程序在开始执行前,分配 静态存储空间,并进行初始化,如果没有指定数据对象的初始值,则每个标题 的值 初始化为零。这样的数据对象在程序结束前一直存在。

(2) 程序在每一个程序块的入口分配动态存储空间。如果没有指定数据初始值,它的初始内容不确定,可能上次程序使用释放过的内存内容(释放内存本身并不清空内存中的数据,只是标记这块内存操作系统可重新使用)。 这些数据对象在程序块执行完毕前一直存在。

(3) 调用calloc、malloc、realloc时,程序才分配可被程序员人为操纵的存储空间,仅当调用calloc时,空间才被初始化。这样分配的数据对象要用free函数释放。否则将生存到程序结束。

5、堆的原理

静态存储空间存在在于程序的整个执行过程 中,动态 存储空间是后进先出,可用栈实现。

动态存储空间经常与函数调用与返回数据一起使用堆栈。可被人为操纵的存储空间不遵守这个规定。C语言的标准库维持着叫“堆”的空间池来控制被calloc、malloc、realloc函数分配的存储空间。

堆中分配的每块内存都应被free函数释放,free函数要释放内存块,就意味着它必须知道释放多大的内存块,然后调用该函数,并没有将需要释放的内存大小告诉它,因此,必须有一种数据结构记录每个已经分配的内存块的信息,同时在堆内存的多次释放与申请过程中,必须会形成很多内存碎片,形成很多数据对象间的小空间,减少了堆的实际可用空间。

多个内存块(Chunks)通常具有相同的尺寸,从内存块边界地址开始,多个领域( Arena)将众多内存块分割成较小的空间进行分配,超大的内存分配(huge arena)要同时占据多个连续内存块(Chunks)才够用。

分配大小分为3个部分:小的、大的和巨大的,所有分配请求被安排到最接近的大小边界,超大的内存分配大于内存块的一半,这种分配存储在单独的红黑树中。对小型和大型的分配,块分割成页,使用二伙伴(Binary-Buddy)算法作为分割算法,Binary-Buddy在分配内存的时候,首先找到一个空闲内存块,接着把内存块不断的进行对半切分(切分得到的2个同样大小的内存块互为伙伴),直到切出来的内存块刚好满足分配需求为止,合并的时候,只有伙伴才能合并为一个新的内存块。

小型和大型分配通过反复将分配大小折半,最后走到一个内存页,但只能合并的方式是分裂过程的相反操作,运行的状态信息作为页面映射存储为每个内存块(Chunks)的开始处,通过分开存储这些信息,页面只接触它们使用的部分,同时对于超过一半的页,但没有超过一半的内存块的大型分配,这种做法同样高效和安全。

小型分配分为三个类:小、量子尺寸(quantum-spaced)和子页,根据数据类型的不同,现代架构要求内存对齐,malloc(3)要求返回适合边界的内存时,在糟糕的情况下,对齐要求被称为量子尺寸(通常是16字节)。如下图所示:

jemalloc分配机制包括领域(arena)、块(chunk)、执行(bin)、运行(run)、线程缓存等部分,jemalloc使用多个分配领域(Arena)将内存分而治之,以块(chunk)作为具体进行内存分配的区域,块(chunk)以页(page)为单位进行管理,每个块(chunk)的前几个 页(page)用于存储后面所有页(page)的状态,后面的所有页(page)则用于进行实际的分配。执行(bin)用来管理各个不同大小单元的分配,每个执行(bin)通过对它对应的运行(run)操作来进行分配的,一个运行(run)实际上就是块(chunk)里的一块区域 ,在分配内存时首先从线程对应的私有缓存空间(tcache)中找。

 但现代的处理器都是多处理器,并行计算已经慢慢成为一种趋势,多线程运算不可避免,为提高malloc在多线程环境的效率和安全性,增强多线程的伸缩性,Jason Evans 在他的《A Scalable Concurrent malloc(3) Implementation for FreeBSD》一文中提出了并发状态的malloc机制,jemalloc使用多个分配领域(Arena)减少在多处理器系统中的线程之间的锁竞争,虽然增加一些成本,但更有利于提供多线程的伸缩性。现代的多处理器在每个高速缓存线(per-cache-line )的基础上提供了内存的一致性视图,如果两个线程同时运行在不同的处理器上,但在同一缓存线(cache-line)操纵不同的对象,那么处理器必须仲裁缓存线(cache-line)的所有权。

6、分配机制

malloc函数是内存分配机制的核心,realloc函数内部通过调用malloc函数申请更大的内存空间,calloc函数也是如此,先通过调用malloc函数申请空间,然后将空间初始化为0。

通常来说,在大多数操作系统(流行的UNIX/LINUX系统、MAC OS以及WINDOWS)中,malloc函数的运作原理为:它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函 数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的内存碎片,当用户申请一个大的内存片段,那么空闲链上没有供分配的内存了,malloc函数在空闲链上整理各内存片段,将相邻的小空闲块合并成较大的内存块等等,最大可能得在内存中腾出需要的空闲空间返回,如果努力失败将返回NULL指针。

在多处理器中,内存分配如何减少程序的多个线程的锁竞争?可采用在每个分配器中放置一个锁,为分配器准备多个领域,通过对线程标识的HASH计算将各个线程分配到这些领域中,如下图所示:

jemalloc使用的是比HASH更具弹性的算法将线程分派到领域中,在FREEBSD中,默认情况下,单处理器使用一个领域,而多处理器中使用相当于处理器4倍数量的领域。

当线程分配器第一次分配或释放内存时,被分派一个领域,但不是通过线程标识的HASH,而是循环的方式,每个区域尽量保证被分派的线程数相等,没用的HASH的原因在于,做到线程标识符(就是线程指针)的可靠的伪随机HASH非常困难。线程本地存储(TLS)对高效实现循环领域非常重要,每个线程的领域需要一个存储位置,在一些不支持TLS的架构上,仍需要使用线程标识HASH,因此使用pthreads库的TSD替代TLS解决这一问题。

mmap函数和sbrk函数担负malloc等分配内存的函数向内核申请内存的任务,mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零,而sbrk增加程序可用数据段空间。

内存块(chunk)的尺寸默认为2M,分配内存块时,基址是尺寸的常数倍,这就是边界对齐,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。各个硬件平台对存储空间的处理上有很大的不同,一些平台对某些特定类型的数据只能从某些特定地址开始存取,边界不对齐将导致读取效率下降很多。

以freebsd10.0系统为例,freebsd采用的是jemalloc分配机制,它能尽量减少内存碎片,提供可伸缩的并发支持,jemalloc在2005首次被引入到freebsd的libc库的分配器。在下图的分析比较中,可以看到,jemalloc分配机制在众多内存分配机制中(最右边的直方框),性能是最好的,

jemalloc使用多个分配领域(Arena)将内存分而治之,以减少在多处理器系统中的线程之间的锁竞争,虽然增加一些成本,但更有利于提供多线程的伸缩性。现代的多处理器在每个高速缓存线(per-cache-line )的基础上提供了内存的一致性视图,如果两个线程同时运行在不同的处理器上,但在同一缓存线(cache-line)操纵不同的对象,那么处理器必须仲裁缓存线(cache-line)的所有权。

如下图所示,被不同线程使用的2个分配器在物理高速缓存上共享同一根缓存线,如果几个线程同时修改2个分配器,处理器得决定这根缓存线归哪个线程使用。

这种看似合理但实质有可能造成低效的高速缓存线路共享会导致严重的性能下降。解决这个问题的方法之一是填充分配(pad allocation),但填充分配与让数据对象尽可能紧密的目标背道而驰,它会引起严重的内存碎片,jemalloc使用一个替代方案,依赖于多个分配领域(Arena)来减少问题,它让应用程序编写者自己进行填充分配(pad allocation),从而在性能的关键代码、线程分配对象的代码以及将对象传递给多个其他线程中,有意避免缓存线程共享机制带来的影响。

C指针原理(42)-内存管理与控制相关推荐

  1. iOS底层原理之内存管理

    文章目录 定时器 CADisplayLink.NSTimer GCD定时器 内存管理 iOS程序的内存布局 Tagged Pointer OC对象的内存管理 拷贝 引用计数的存储 dealloc 自动 ...

  2. 操作系统核心原理-5.内存管理(中):分页内存管理

    在上一篇介绍的几种多道编程的内存管理模式中,以交换内存管理最为灵活和先进.但是这种策略也存在很多重大问题,而其中最重要的两个问题就是空间浪费和程序大小受限.那么有什么办法可以解决交换内存存在的这些问题 ...

  3. Block 本质、实现原理、内存管理、循环引用、__block等

    一.Block介绍 1.1概念: 将函数及其执行上下文封装起来的对象 底层用struct实现 1.2block实现原理: a .新建项目 代码放入file.m中 b.打开终端cd到项目目录下 c.敲c ...

  4. linux内存映射原理,Linux内存管理实践-使用fault()实现内存映射

    内核态与用户态进行数据交互通常是这样一种模型:内核利用自身的特权通过特定的服务程序采集.接收和处理数据:接着,用户态程序和内核服务程序进行数据交互,或接收内核态的数据,或向内核态写入数据.通过传统的那 ...

  5. java gc原理_Java内存管理以及GC工作原理

    1.内存管理简介 内存管理的职责为分配内存,回收内存. 没有自动内存管理的语言/平台容易发生错误. 典型的问题包括悬挂指针问题,一个指针引用了一个已经被回收的内存地址,导致程序的运行完全不可知. 另一 ...

  6. 操作系统原理之内存管理(第四章第一部分)

    内存管理的⽬标:实现内存分配和回收,提高内存空间的利用率和内存的访问速度 一.存储器的层次结构 寄存器:在CPU内部有一组CPU寄存器,寄存器是cpu直接访问和处理的数据,是一个临时放数据的空间. 高 ...

  7. 操作系统核心原理-5.内存管理(下):段式内存管理

    一.分页系统的缺点 分页系统存在的一个无法容忍,同时也是分页系统无法解决的一个缺点就是:一个进程只能占有一个虚拟地址空间.在此种限制下,一个程序的大小至多只能和虚拟空间一样大,其所有内容都必须从这个共 ...

  8. JavaScript的工作原理:内存管理+如何处理4个常见的内存泄漏

    本篇译文,删减了原文中一些无关紧要的内容,可以让大家花更少的阅读时间. 原文地址:https://blog.sessionstack.com/how-javascript-works-memory-m ...

  9. 操作系统原理之内存管理(第四章第二部分)

    一.基本分页存储管理方式 1.分⻚存储管理的基本原理: 页:将⼀个进程的逻辑地址空间分成若⼲个⼤⼩相等的⽚ 页框:将物理内存空间分成与⻚⼤⼩相同的若⼲个存储块 分⻚存储:将进程中的若⼲⻚分别装⼊多个可 ...

最新文章

  1. linux mysql清除数据库所有表_MySQL修复指定数据库下的所有表
  2. [祝]微软山西DotNet俱乐部(高校行系列)山西大学公益讲座
  3. 【DIY】热水器升级加装远程wifi控制功能,esp8266远程红外控制热水器启动,稳定连续运行4天了,功能展示终稿...
  4. 手把手教你手动创建线程池
  5. MySQL时间戳(毫秒/秒)与日期格式的相互转换
  6. tensorflow 之 最近用到的几个小操作tf.reshape,tf.convert_to_tensor,tf.where
  7. kaptchaCode(验证码)
  8. shell 删除乱码文件
  9. 东北天到ecef的变换_GNSS学习笔记-坐标转换
  10. 512M内存编译php出错
  11. 08 在C#程序中使用注释测试分析 1214
  12. MongoDB 安装与基本命令
  13. MVP 模式实例解析(转)
  14. Android 横屏启动activity,点击屏幕的单击、双击和长按事件
  15. UEFI原理与编程(七):包及.dsc、.dec、.fdf文件
  16. 《德鲁克管理思想精要》读书笔记5 - 人事、创新、创业
  17. Linux中man手册的安装以及使用详解
  18. intel英特尔架构
  19. 分级聚类算法(集体智慧编程)
  20. ibm服务器修复安装win7系统,联想thinkpad无法开机重装win7,教你重装系统攻略

热门文章

  1. FileInputStream与FileOutputStream 复制文件例子代码
  2. Struts2的自动装配
  3. Android自定义顶部栏及侧滑菜单和fragment+viewpag滑动切换的实现
  4. Day 1 用户交互
  5. B1208 [HNOI2004]宠物收养所 平衡树||set (滑稽)
  6. doctype是什么?
  7. JAVA中List的几个方法
  8. linux第9天 UDP
  9. sql中 set 和select 的区别
  10. QlikView Script -组合键处理