读书笔记-程序员的自我修养(九)

内存:一个承载程序运行的介质,也是程序进行各种运算和表达的场所

  • 程序的内存(进程的地址空间)布局
  1. :用于维护函数调用的上下文,执行 函数调用 的功能

  2. :用于程序 动态分配 的内存区域,也是 malloc 或 new 分配的内存区域

  3. 可执行文件映像:存储着可执行文件在内存里的映像,装载器

  4. 保留区:保留区并不是一个单一的内存区域,而是对内存中受到保护而禁止访问的内存区域的总称,比如有些地址不允许访问等等

  • 栈与调用惯例

    • 作用:保存了一个函数调用所需要的 维护信息(函数返回地址和参数,临时变量,保存的上下文),常称为 堆栈帧(Stack Frame)

    • 函数的调用方和被调用方对于函数如何调用,遵循 惯用惯例,包括:函数参数的传递顺序和方式,栈的维护方式,名字修饰策略。常用的管理模式有:cdecl(函数调用方),stdcall(函数本身),fastcall(函数本身),pascal(函数本身)

  • 堆与内存管理

    • 普及:全局变量没有办法动态地产生,只能在编译的时候定义

    • 形象解释:运行库相当于是向操作系统 “批发” 了一块较大的对控件,然后 “零售” 给程序用。当全部 “售完” 或程序有大量的内存需求时,再根据实际需求向操作系统 “进货”

    • Linux 提供两种系统调用:brk 和 mmap,Window 提供四种系统调用:HeapCreate,HeapAlloc,HeapFree,HeapDestroy,对向上封装的函数就是著名的 malloc

    • 要点

      • 堆里的同一片内存不能重复释放两次

      • malloc 申请的内存,进程结束后,所有资源都会回收,因此不存在了

      • malloc 申请的内存,逻辑上是连贯的,物理上不一定

    • 堆分配算法:如何管理一大块连续的内存空间,能够按照需求分配、释放其中的空间

      • 空闲链表

        核心原理:在堆里的每一个空闲空间的开头(或结尾)有一个头(header),头结构里记录了上一个(prev)和下一个(next)空间块的地址,这样所有空闲块形成了一个链表,申请的时候,查找符合大小的空闲块,然后将这块空闲空间从链表中”删除”,供使用,回收的时候,需要知道头指针和空间大小,因此在申请的时候,往往申请 K 空间,分配 K + 4 的空间,多余的 4 个记录大小,但这样一旦堆操作越界,破坏了这 4 个里面的数据,整个对就无法正常工作了

      • 位图

        将整个堆划分为大量的 块(Block),每个块大小相同,申请的时候,总是分配整数个块的空间,且称已经分配的第一块为 头(head),其余的称为 主体(Body),优点:速度快,整个堆的空闲信息存储在一个数组中,因此访问该数组时cache容易命中,稳定性好:为了避免用户越界读写破坏,只需要简单的备份下位图即可,缺点:分配容易产生碎片(平均划分),位图很大,cache命中率降低

      • 对象池

        以申请空间的大小作为分水岭,综合应用上述两种算法

如需转载,请注明出处