栈
- 栈是机器系统提供的数据结构, 现代计算机(串行执行机制)都直接在代码底层支持栈的数据结构, 有专门的寄存器指向栈所在的地址, 有专门的机器指令完成数据入栈出栈的操作
- 栈的特点是效率高, 支持的数据类型有限, 一般是整数, 指针, 浮点数等系统直接支持的数据类型, 并不直接支持其他的数据结构
- 子程序的调用就是直接利用栈完成的, 机器的 call 指令里隐含了把返回地址压入栈, 然后跳转至子程序地址的操作, 子程序中的 ret 指令则隐含了从堆栈中弹出返回地址并跳转到返回地址的操作
- C/C++ 程序中的自动变量存入栈中, 这也就是为什么当函数返回时, 该函数的自动变量自动失效的原因
- 栈的特点: 快速, 高效, 但有限制, 数据不灵活, 对于进程/线程是唯一的
- 栈空间分静态分配和动态分配两种: 静态分配是编译器完成的, 比如自动变量(auto), 动态分配由 alloca 函数完成, 均自动释放, 也就没有释放函数, 为可移植的程序起见, 栈的动态分配操作是不被鼓励的
- 函数调用时会在栈上有一系列的保留现场及传递参数的操作, 当一个函数调用完返回后它会释放该函数中所有的栈空间, 栈的存储地址连续, 不会产生内存碎片
- 栈的空间大小有限定, vc 中的缺省是2MB, 栈不够用的情况一般是程序中分配了大量数组或递归函数层次太深
- 栈是先入后出的数据结构, 一般是由高地址向低地址方向生长
- 栈使用的是一级缓存, 通常被调用时处于存储空间中, 调用完毕立即释放
堆
- 堆是由 C/C++ 函数库提供的, 并不是由系统(无论是机器系统还是操作系统)支持的
- C中的 malloc, free 函数或C++中的 new, delete 函数维护了一套内部的堆数据结构
- 动态内存的分配: 先从内部堆中寻找可用的内存, 若失败则利用系统调用来动态增加程序数据段的内存大小, 新分配得到的内存首先被组织进内部堆中去, 然后再以适当的形式返回给调用者
- 动态内存的释放: 内存被返回内部堆结构中, 且可能会被适当的处理, 比如和其他空闲空间合并成更大的空闲空间, 以更适合下一次内存分配
- 堆的特点: 灵活, 方便, 数据适应面广泛, 但由于堆中的数据需要通过指针进行存取, 效率低, 对于进程/线程不一定唯一, 不同堆分配的内存无法互相操作
- 堆空间的分配总是动态的, 虽然程序结束时, 所有的数据空间都会被操作系统回收, 但是精确的内存申请/释放匹配是良好程序的基本要素, 否则可能产生内存泄漏
- 频繁地调用 malloc 和 free 会产生内存碎片, 因为 C 分配动态内存时是寻找匹配的内存的
- 堆内存区的地址是不连续的, 它是系统将空闲内存块链接起来的链表, 用户用 new/malloc 请求分配时, 找到第一个满足大小要求的块从链表中删除此节点, 然后分给用户
- 堆的数据结构可以被看成是一棵树, 如堆排序
- 堆存放在二级缓存中, 生命周期由虚拟机的垃圾回收算法来决定, 并不是一旦成为孤儿对象就能被回收, 所以调用这些对象的速度要相对来得慢一些
内存分配机制实际上相当于一个内存分配的缓冲池(Cache), 使用这套机制有如下若干原因:
- 系统调用可能不支持任意大小的内存分配, 有些系统的系统调用只支持固定大小及其倍数的内存请求(按页分配), 这样对于大量的小内存分配来说会造成浪费
- 系统调用申请内存可能是代价昂贵的, 可能涉及用户态和内核态的转换
- 随意的内存分配和释放操作会造成内存碎片