iOS内存分配:堆、栈、全局区、常量区、代码区
前言
内容参考:
NSString存储管理--NSTaggedPointerString
分析
iOS内存分配.png1. 代码区
代码区是用来存放函数的二进制代码,也就是,它是可执行程序在内存中的镜像。代码段需要防止在运行时被非法修改,所以只允许读取操作,而不允许写入操作。
2. 全局(静态)区
- 数据区:数据段用来存放可执行文件中已经初始化的全局变量,也就是用来存放静态分配的变量和全局变量。
- BSS区:BSS段包含了程序中未初始化的全局变量
3. 常量区
常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量。
4. 堆(heap)区
堆是由程序员分配和释放,用于存放进程运行中被动态分配的内存段。它大小不固定,可动态扩张和缩减。
5. 栈(stack)区
栈是由编译器自动分配释放来管理内存。用户存放程序临时创建的变量、存放函数的参数值、局部变量等。由于栈的先进后出特点,所以特别适合用来做保存/恢复现场的操作。从这个吧意义上,我们可以把栈看做一个临时寄存、交换的内存区。
static 修饰的属性始终保存到常量区。
上述几种内存区域中,数据段、BSS、堆通常都是被连续存储的-内存位置上的连续(并不是堆链式存储的内存区域)。而代码段和栈往往会被独立存放。
栈是向低地址扩展的数据结构,是一块连续的内存区域。堆是向高地址扩展的数据结构,是不连续的内存区域。
内存模型 Heap、Stack
以下内容引用自阮一峰老师的 汇编语言入门教程。
对于堆 heap
和 栈 stack
描述更加详细易懂。
1. 堆 Heap
寄存器只能存放少量的数据,大多数时候, CPU
还要指挥寄存器,直接跟内存交换数据,所以,除了寄存器,还必须了解内存怎么存储数据。
程序运行的时候,操作系统会给它分配一块内存,用来存储程序和运行产生的数据,比如从 0x1000
到 0x8000
,起始地址是较小的那个地址,结束地址是较大的那个地址。
程序运行过程中,对于动态的内存占用请求(比如新建对象),系统就会从预先分配好的那段内存中,划出一部分给用户,具体规则是从起始地址开始划分(实际上,起始地址会有一段静态数据,这里忽略)。举例来说,用户要求得到10个字节内存,那么从起始地址 0x1000
开始给他分配,一直分配到 0x100A
,如果在要求得到 22
个字节,那么就分配到 0x1020
。
这种因为用户主动请求而划分出来的内存区域,叫做 Heap(堆)
。它由起始位置开始,从低位(地址)向高位(地址)增长。 Heap
的一个重要特点就是不会自动消失,必须手动释放,或者由垃圾回收机制来回收。
2. 栈 Stack
除了 Heap
外,其他的内存占用叫做 Stack(栈)
。简单来说,Stack
是由于函数运行而临时占用的内存区域。
请看下面的例子:
int main() {
int a = 2;
int b = 3;
}
上面代码中,系统开始执行 main
函数时,会为它在内存里面建立一个 帧(frame)
,所有的 main
的内部变量(比如 a
和 b
)都保存在这个 帧
里面。main
函数执行结束后,该帧就会被回收,释放所有的内部变量,不再占用空间。
如果函数内部调用其他函数,会发生什么情况呢?
int main() {
int a = 2;
int b = 3;
return add_a_and_b(a,b);
}
上面代码中,main
函数调用了 add_a_and_b
函数。执行到这一行的时候,系统也会为 add_a_and_b
新建一个 帧(frame)
,用来存储它的内部变量。也就是说,此时同时存在两个帧:main
和 add_a_and_b
。一般来说,调用栈有多少层,就有多少帧。
等到 add_a_and_b
运行结束,它的帧就会被回收,系统会回到刚才 main
函数中断执行的地方,继续往下执行。通过这种机制,就实现了函数的层层调用,并且每一层都能使用自己的本地变量。
所有的帧都存放在 Stack
,由于帧是一层层叠加的,所以 Stack
叫做 栈。生成新的帧,叫 入栈
,英文单词是 push
;栈的回收叫 出栈
,英文是 pop
。Stack
的特点就是,最晚入栈的帧最早出栈(因为最内层的函数调用,最先结束执行),这种叫做 后进先出 的数据结构。每一次函数执行结束,就自动释放一个帧,所有的函数执行结束,整个 Stack
就都释放了。
Stack
是由内存区域的结束地址开始,从高位(地址)向地位(地址)分配。比如,内存区域的结束地址是 0x8000
,第一帧假定是 16
字节,那么下一次分配的地址就会从 0x7FF0
开始;第二帧假定需要 64
字节,那么地址就会移到 0x7FB0
。
关于 NSString
的内存分配
NSString存储管理--NSTaggedPointerString 文章中详细说明了问题,我们并不能单纯的通过打印内存地址,来判断数据存储的区域在常量,或者堆区、栈区。