IOS 堆、栈的使用与区别

  • 数据结构
  • 内存管理
  • 堆、栈的区别
  • 管理方式
  • 体型、性能
  • 存储内容
  • 参考


数据结构

堆、栈是两种数据结构。

栈是一种线性的数据结构,存储和访问数据时,都只能访问栈的一端。数据访问为 FILO(先进后出)。

堆是一种特殊的二叉树,(最大堆)具有以下两个性质:

  1. 每个节点的值 >= 其每个子节点的值。
  2. 树完全平衡(任意节点的左右子树的高度差值 <= 1),最后一层的叶子节点都位于最左侧。 堆可以用树(指针节点)来实现,也可以用数组来实现:

ios 栈区 ios堆和栈的区别_ios 栈区



  1. 注:可以看到,数据结构中的堆维护了一个较为复杂的结构,但实际内存中的堆几乎只是借用了堆的名字,并不会维护堆的性质与结构。

内存管理

操作系统中,一般是指内存管理的栈区和堆区,栈区由系统自动控制,堆区是在程序中使用诸如 new/malloc 的指令进行调用的。
一个由 C/C++ 编译的程序占用的内存分为五个部分:栈区、堆区、全局区/静态区(存放全局变量或 static 静态变量)、常量区(宏或const类型)、代码区(函数体的二进制代码)。
有一个很好的例子:

int a = 0; // 全局初始化区
char *p1;  // 全局未初始化区,两位在全局区的不同位置
int main () {
	int b; // 栈
	char s[] = "abc"; // 栈
	char *p2; // 栈
	char *p3 = "123456"; // 123456\0在常量区,p3在栈上。
	static int c =0; // 全局(静态)初始化区
	p1 = (char *)malloc(10);  // 堆
	p2 = (char *)malloc(20);  // 堆
}

IOS 中(OC、Swift),堆、栈的使用与内存管理与 C 基本相同。其内存分配的示意图为:

ios 栈区 ios堆和栈的区别_堆栈_02


注:栈区的大小提前分配好,然后栈指针指向栈空间地址最高处,压栈的数据向低地址区生长,出栈时只需将栈指针向高地址移动相应大小即可。

多线程的环境下,同一进程的线程有自己完全独立的栈,但共享堆区。

堆、栈的区别

管理方式

栈的空间由系统自动分配与回收,如正在运行的程序定义了一个变量 int a; 则系统自动在运行该程序的 进程/线程 的栈中开辟一个 int 整型变量的空间呈装变量 a。

堆的空间由程序进行分配与回收,如程序使用:
char *p = (char *)malloc(10*sizeof(char)) 获取了一个大小为 10 个 char 类型大小的空间,并由指针 p 存放空间的首地址(指令相当于创建了一个大小为 10 的 char 类型数组)。数组的数据内容在堆中,而数组的指针 p 在栈中。
程序执行完毕,指针 p 会被自动释放,但堆中的空间需要使用 free(p) 进行释放,否则将按照系统生命周期的垃圾回收机制进行处理,如果忘记手动释放,容易造成内存泄漏(堆中空间被占用,而程序中的指针已经释放,导致空间无法被访问而浪费)。

体型、性能

栈是内存上一块较小的区域,其访问方式固定(只支持从顶部添加、移除数据),运行速度较快,且没有碎片。

堆的空间比栈大得多,可在运行时动态分配内存。由于其分配的方式可任意(只要低地址处有足够的空间就可以分配,释放的时候也是直接空出对应的空间即可),因此系统需要维护一个链表来追踪堆区哪些部分被占用(以及被谁使用)。
这种块组织方式,就很容易在不断的分配和释放之中,形成小碎片(块与块之间的不足以被任何块使用的空间)。
在多线程的环境下,还需要考虑多个线程同时访问堆区的同一个块时,需要给块加锁(一致性的要求),这又是一大笔开销。

堆分配 根据实际情况不同,有许多分配策略,但其性能始终是不及栈的。

存储内容

栈一般用来存储函数参数、局部变量等数据。
函数调用时,子函数的压栈顺序有其物理意义,而不仅仅是逻辑意义:

在函数调用时,第一个进栈的是主函数中 函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,然后是函数中的局部变量。(注意静态变量是不入栈的。)
本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序将从该点继续运行。

堆一般用来存储引用类型的对象数据。


参考
  1. 《C++数据结构与算法 (第4版)》 – Adam Drozdek
  2. iOS中堆和栈的使用

个人总结,敬请指正。