1. 介绍
在iOS
中,内存主要分为栈区
、堆区
、全局区
、常量区
、代码区
五大区域,如下图所示:
2. 栈区
2.1 介绍
- 栈是系统的数据结构,其对应的进程或者线程是唯一的
- 栈是从
高地址向低地址
扩展的数据结构 - 栈是一块连续的内存区域,遵循
FILO
,先进后出原则 - 在iOS中一般以
0x7
开头,在运行时分配 - 编译器自动分配释放,不会产生内存碎片,快速高效
- 内存大小有限制,数据不灵活(主线程栈是
1MB
,其他线程是512KB
)
2.2 存储的数据
栈区的内存是由编译器自动分配并释放的,主要用来存储:
- 函数内部定义的局部变量和数组
- 函数的参数
栈区的内存空间是由系统管理,在调用的时候开辟空间,函数调用完成,就收回空间。
3. 堆区
3.1 介绍
- 和栈相反,堆是低地址向高地址扩展的数据结构
- 堆是不连续的内存区域,类似于链表,遵循先进先出原则
- 堆的地址空间在
iOS
中以0x6
开头,一般在运行时动态分配空间 - 需要手动管理,容易产生内存碎片
3.2 存储的数据
堆区主要由开发者动态分配和释放,如果开发者不释放,程序结束后,则由操作系统回收
- OC中使用
alloc
或者使用new
开辟空间创建对象 - C语言中使用
malloc
、calloc
、realloc
分配的空间,需要free释放
注意:当需要访问堆中内存时,一般需要先通过对象读取到栈区的指针地址,然后通过指针地址访问堆区
4. 全局区 (静态区,.bss & .data
)
- 全局区是在编译时分配的内存空间
- 全局区的地址空间在iOS中一般以
0x1
开头 - 程序运行中,内存中的数据一致存在,程序结束后由系统释放
-
.bss(BSS)
区存放未初始化的全局变量和静态变量 -
.data
数据区存放已经初始化的全局变量和静态变量
其中,全局变量是指变量值可以在运行时被动态修改,而静态变量是static
修饰的变量,包含静态局部变量和静态全局变量。
5. 常量区 (.rodata
)
常量区是编译时分配的内存空间,在程序结束后由系统释放,主要存放已经使用了的,且没有指向的字符串常量。
字符串常量可能会在程序中多次使用,所以在程序运行之间就会提前分配内存。
6. 代码区(.text
)
代码区是编译时分配主要用于存放程序运行时的代码,代码会被编译成二进制存进内存的。
7. 验证
举个例子,看看下面的代码,变量在内存中是如何分配的:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSInteger i = 123;
NSLog(@"i的内存地址:%p", &i);
NSString *string = @"CJL";
NSLog(@"string的内存地址:%p", string);
NSLog(@"&string的内存地址:%p", &string);
NSObject *obj = [[NSObject alloc] init];
NSLog(@"obj的内存地址:%p", obj);
NSLog(@"&obj的内存地址:%p", &obj);
}
输出结果如下:
2021-01-19 20:38:48.645299+0800 KVC[42501:2211601] i的内存地址:0x7ffee4107138
2021-01-19 20:38:48.645439+0800 KVC[42501:2211601] string的内存地址:0x10baf9040
2021-01-19 20:38:48.645553+0800 KVC[42501:2211601] &string的内存地址:0x7ffee4107130
2021-01-19 20:38:48.645663+0800 KVC[42501:2211601] obj的内存地址:0x600001ba8110
2021-01-19 20:38:48.645761+0800 KVC[42501:2211601] &obj的内存地址:0x7ffee4107128
- 对于局部变量i,地址是
0x7
开头的,存放在栈区 - 对于字符串对象
string
,string
的对象地址是0x10baf9040
,存放在常量区,存放string
对象指针的地址是0x7ffee4107130
,存放在栈区 - 对于
alloc
创建的对象obj
,对象的内存地址是0x600001ba8110
,存放在堆区,存放obj
对象指针的地址是0x7ffee4107128
,存放在栈区
8. 函数栈&栈帧
- 函数栈又称为栈区,在内存中从
高地址往低地址
分配,与堆区相对 -
栈帧
是函数(运行中)占用的一块独立的连续内存区域 - 应用中新创建的每个线程都有专用的栈空间,栈可以在线程期间自由使用。而线程中有千千万万的函数调用,这个函数共享一个栈空间。
栈帧
就是这个函数在栈空间里独占的部分,所有的栈帧就组成了这个线程完整的栈。 - 函数调用是发生在
栈上
的,每个函数的相关信息(例如局部变量
、调用记录
等)都存储在一个栈帧中,每执行一个函数调用,就会生成一个与其相关的栈帧,然后将其栈帧压入函数栈,当函数执行完毕,对应的栈帧也会出栈并释放掉。
如下图所示,是经典图 - ARM
的栈帧布局方式:
- 其中
main stack frame
为main
函数的栈帧,func1 stack frame
为当前函数(被调用者)的栈帧 - 栈底是高地址,向下增长
-
FP
是栈基址,指向栈帧起始地址,SP
是栈顶指针,指向栈顶位置 -
ARM压栈
是有顺序的,依次为当前函数指针PC
、返回指针LR
、栈指针SP
、栈基址FP
、传入参数个数及指针、本地变量和临时变量。如果函数准备调用另一个函数,跳转之前临时变量区先要保存另一个函数的参数 -
ARM
也可以用栈基址和栈指针明确标示栈帧的位置,栈顶指针SP
一直移动,ARM
的特点是,两个栈空间内的地址(SP+FP)
前面,必然有两个代码地址(PC+LR)
明确标示着调用函数位置内的某个地址。
注意:一般情况下是不需要考虑堆栈的带下,但是事实上它们都是有限制的,过多的递归会导致栈溢出,过多的
alloc
对象会导致堆溢出。
推荐 阮一峰-汇编语言入门教程。
网友评论