内存:一个承载程序运行的介质,也是程序进行各种运算和表达的场所
- 程序的内存(进程的地址空间)布局
栈:用于维护函数调用的上下文,执行 函数调用 的功能
堆:用于程序 动态分配 的内存区域,也是 malloc 或 new 分配的内存区域
可执行文件映像:存储着可执行文件在内存里的映像,装载器
保留区:保留区并不是一个单一的内存区域,而是对内存中受到保护而禁止访问的内存区域的总称,比如有些地址不允许访问等等
栈与调用惯例
作用:保存了一个函数调用所需要的 维护信息(函数返回地址和参数,临时变量,保存的上下文),常称为 堆栈帧(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命中率降低
对象池
以申请空间的大小作为分水岭,综合应用上述两种算法