iOS底层-内存分区与布局

2,769 阅读4分钟

前言

内存管理是开发过程中不可忽视的部分,出现的很多问题都和内存有关。我们都知道内存的五大区,那么它是怎样布局的,接下来本将对它进行讲解。

内存分区与布局

  • 4G手机为例的内存五大区与布局如图所示:

截屏2021-10-20 17.18.46.png

栈区(Stack)

  • 栈区是一块连续的内存空间,它的结构是从高地址往低地址拉伸,遵循先进后出(FILO)原则
  • 栈区存储的是局部变量函数方法参数指针
  • 栈区的地址空间一般是以0x7开头
  • 栈区由编译器自动分配内存和释放

举例:

- (void)testStack{
    // 栈区
    int a = 10;
    int b = 20;
    NSObject *object = [NSObject new];
    NSLog(@"a == %p",&a);
    NSLog(@"b == %p",&b);
    NSLog(@"object == %p",&object);
    NSLog(@"%lu",sizeof(&object));
    NSLog(@"%lu",sizeof(a));
}

打印结果如下:

截屏2021-10-20 22.42.43.png

  • 可以观察出栈区内存连续,且内存是从高地址往低地址扩展,内存是0x7开头

堆区(Heap)

  • 堆区是一块不连续的内存空间,它的结构是从低地址向高地址扩展
  • 堆区存储的是对象,需要开辟空间的东西,栈区内存比较小,堆区比较大,所以在堆区开辟空间
  • 堆区地址空间一般以0x6开头

举例:

- (void)testHeap{
    // 堆区
    NSObject *object1 = [NSObject new];
    NSObject *object2 = [NSObject new];
    NSObject *object3 = [NSObject new];
    NSObject *object4 = [NSObject new];
    NSObject *object5 = [NSObject new];
    NSObject *object6 = [NSObject new];
    NSObject *object7 = [NSObject new];
    NSObject *object8 = [NSObject new];
    NSObject *object9 = [NSObject new];
    NSLog(@"object1 = %@",object1);
    NSLog(@"object2 = %@",object2);
    NSLog(@"object3 = %@",object3);
    NSLog(@"object4 = %@",object4);
    NSLog(@"object5 = %@",object5);
    NSLog(@"object6 = %@",object6);
    NSLog(@"object7 = %@",object7);
    NSLog(@"object8 = %@",object8);
    NSLog(@"object9 = %@",object9);
}

结果如下:

截屏2021-10-20 22.56.27.png

  • 从打印结果中可以看出来堆区的内存是不连续的,且是0x6开头

全局区(静态区)

  • 全局区又分为.bss段.data段,内存地址一般由0x1开头:
    • Bss段: 未初始化的全局变量,静态变量,
    • Data段: 已初始化的全局变量,静态变量
  • 举例:
    - (void)globalTest {
        // 全局区
        NSLog(@"************ bss ************");
        NSLog(@"bssA == \t%p",&bssA);
        NSLog(@"bssB == \t%p",&bssB);
        NSLog(@"bssStr == \t%p",&bssStr);
      
        NSLog(@"************ data ************");
        NSLog(@"dataA == \t%p",&dataA);
        NSLog(@"dataB == \t%p",&dataB);
        NSLog(@"dataStr == \t%p",&dataStr);
    }
    
    • 运行结果如下:

      截屏2021-10-20 23.29.58.png

静态安全测试

  • 经常有人说静态区也称作静态安全区,下面我们就来验证它为什么安全,创建一个WSPerson类,以及它的分类:

    // WSPerson.h
    static int ws_number = 100;
    
    @interface WSPerson : NSObject
    - (void)eat;
    + (void)sleep; 
    @end
    
    // WSPerson.m
    - (void)eat {
        ws_number++;
        NSLog(@"%s __ %p __ %d", __func__, &ws_number, ws_number);
    }
    + (void)sleep {
        ws_number++;
        NSLog(@"%s __ %p __ %d", __func__, &ws_number, ws_number);
    }
    
    // WSPerson+App.h
    @interface WSPerson (App)
    - (void)cate_test;
    @end
    
    // WSPerson+App.m
    - (void)cate_test {
        ws_number++;
        NSLog(@"%s __ %p __ %d", __func__, &ws_number, ws_number);
    }
    

    ViewController中的调用代码如下:

    - (void)constTest {
        [[WSPerson alloc] eat];
        NSLog(@"%s __ %p __ %d", __func__, &ws_number, ws_number);
        ws_number = 10000;
        NSLog(@"%s __ %p __ %d", __func__, &ws_number, ws_number);
        [[WSPerson alloc] eat];
        NSLog(@"%s __ %p __ %d", __func__, &ws_number, ws_number);
        [WSPerson sleep];
        NSLog(@"%s __ %p __ %d", __func__, &ws_number, ws_number);
        [[WSPerson alloc] cate_test];
        NSLog(@"%s __ %p __ %d", __func__, &ws_number, ws_number);
    }
    

    打印出的结果比较有意思:

    截屏2021-10-22 17.34.24.png

    三个文件ws_number的内存地址不一样,具体是原因需要借助Clang查看三个文件的C++代码:

    截屏2021-10-22 17.41.03.png

    从源码中可以观察到每个文件都有个ws_number的全局变量,且初始值都一样。也就是说在多个文件使用同一个静态变量,系统会各自生成一个相同初始值地址不同的静态变量,这样在各自文件内使用就不会互相干扰,数据比较安全。

常量区(.rodata)

  • 常量区的内存在编译时就已经确定,主要存放已经使用过的,且没有指向的字符串常量(因为字符串常量可能在程序中多次被使用,所以在程序运行之前就会提前分配内存)。常量区的常量在程序结束后,由系统释放

代码区(.text)

  • 存储程序代码,在编译时加载到内存中,代码会被编译成二进制的形式进行存储

其他

    1. 我们可以从图中看出,栈底的内存为0c0000000,转成10进制后等于3GB,还有1G的内存分配给了内核区
    • 内核区:主要进行消息处理以及多线程的操作
    1. 代码区的地址是从0x00400000开始,怎么不是从0x0开始? 因为0x0nil,从nil开始分配会出现问题,所以在代码区之前就有了保留区
    • 保留区:预留给系统处理nil

相关面试题

    1. 为什么说Static修饰的变量不占内存?
    • 答:因为Static修饰的变量修饰的变量在全局区,它不占用自己分配的内存
    1. 栈区的内存根据经验如何定位?
    • 答:栈区的内存是通过sp寄存器定位的,堆区是通过寄存器定位到这一个包含内存的地址,通过地址定位内存的位置,所以决定栈区和堆区的速度,栈区的速度是非常快的