[iOS] 内存五大区

1. 介绍

iOS中,内存主要分为栈区堆区全局区常量区代码区五大区域,如下图所示:

image.png

2. 栈区

2.1 介绍
  • 栈是系统的数据结构,其对应的进程或者线程是唯一的
  • 栈是从高地址向低地址扩展的数据结构
  • 栈是一块连续的内存区域,遵循FILO,先进后出原则
  • 在iOS中一般以0x7开头,在运行时分配
  • 编译器自动分配释放,不会产生内存碎片,快速高效
  • 内存大小有限制,数据不灵活(主线程栈是1MB,其他线程是512KB
2.2 存储的数据

栈区的内存是由编译器自动分配并释放的,主要用来存储:

  • 函数内部定义的局部变量和数组
  • 函数的参数
    栈区的内存空间是由系统管理,在调用的时候开辟空间,函数调用完成,就收回空间。

3. 堆区

3.1 介绍
  • 和栈相反,堆是低地址向高地址扩展的数据结构
  • 堆是不连续的内存区域,类似于链表,遵循先进先出原则
  • 堆的地址空间在iOS中以0x6开头,一般在运行时动态分配空间
  • 需要手动管理,容易产生内存碎片
3.2 存储的数据

堆区主要由开发者动态分配和释放,如果开发者不释放,程序结束后,则由操作系统回收

  • OC中使用alloc或者使用new开辟空间创建对象
  • C语言中使用malloccallocrealloc分配的空间,需要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开头的,存放在栈区
  • 对于字符串对象stringstring的对象地址是0x10baf9040,存放在常量区,存放string对象指针的地址是0x7ffee4107130,存放在栈区
  • 对于alloc创建的对象obj,对象的内存地址是0x600001ba8110,存放在堆区,存放obj对象指针的地址是0x7ffee4107128,存放在栈区

8. 函数栈&栈帧

  • 函数栈又称为栈区,在内存中从高地址往低地址分配,与堆区相对
  • 栈帧是函数(运行中)占用的一块独立的连续内存区域
  • 应用中新创建的每个线程都有专用的栈空间,栈可以在线程期间自由使用。而线程中有千千万万的函数调用,这个函数共享一个栈空间。栈帧就是这个函数在栈空间里独占的部分,所有的栈帧就组成了这个线程完整的栈。
  • 函数调用是发生在栈上的,每个函数的相关信息(例如局部变量调用记录等)都存储在一个栈帧中,每执行一个函数调用,就会生成一个与其相关的栈帧,然后将其栈帧压入函数栈,当函数执行完毕,对应的栈帧也会出栈并释放掉。

如下图所示,是经典图 - ARM的栈帧布局方式:

image.png

  • 其中main stack framemain函数的栈帧,func1 stack frame 为当前函数(被调用者)的栈帧
  • 栈底是高地址,向下增长
  • FP是栈基址,指向栈帧起始地址,SP是栈顶指针,指向栈顶位置
  • ARM压栈是有顺序的,依次为当前函数指针PC、返回指针LR、栈指针SP、栈基址FP、传入参数个数及指针、本地变量和临时变量。如果函数准备调用另一个函数,跳转之前临时变量区先要保存另一个函数的参数
  • ARM也可以用栈基址和栈指针明确标示栈帧的位置,栈顶指针SP一直移动,ARM的特点是,两个栈空间内的地址(SP+FP)前面,必然有两个代码地址(PC+LR)明确标示着调用函数位置内的某个地址。

注意:一般情况下是不需要考虑堆栈的带下,但是事实上它们都是有限制的,过多的递归会导致栈溢出,过多的alloc对象会导致堆溢出。

推荐 阮一峰-汇编语言入门教程。

你可能感兴趣的:([iOS] 内存五大区)