内存管理系列—从C语言看OC的内存布局

内存管理系列文章:ios

前言

OC底层实现其实都是C语言的代码,因此想深刻理解iOS的内存管理机制,能够经过了解C语言的内存管理来进一步熟悉OC的内存管理。程序员

内存分类

  • RAM:运行内存,不能掉电储存;
  • ROM:储存性内存,能够掉电储存,例如:内存卡,flash;

RAM和ROM的区别bash

  1. RAM的访问速度要远高于ROM,价格也要高;
  2. CPU只能从RAM直接读取指令;
  3. app程序通常存放于ROM中。启动app时,系统会把开启的app程序从ROM中转移到RAM中

从内存分配看app加载过程

在了解OC的内存分配以前,先看下app的加载过程:数据结构

  1. 当APP没有打开时,ipa或者app文件都是存在于ROM中,即咱们说一个叫Application的文件夹中。
  2. 在APP启动的时候iOS系统会先为app从RAM中分配一个独立的内存空间(即沙盒),app全部的内存操做都在这个独立的沙盒中进行。
  3. 接着系统会先加载二进制代码到内存中,而后加载常量区中的常量,接着加载全局区和静态区(初始化过的静态区和没有初始化过的静态区是分开的)
  4. 以后程序会找main入口函数开始执行代码,在执行代码的过程当中,会建立对象和一些局部变量,其中对象存放在堆中,变量存放在栈上。

内存分区

画图工具没找到太合适的,粗略的作了一张图,经过如下这张图对内存布局有个大概认识:app

区段解释

  1. 程序代码区:代码区用来存放函数体的二进制代码,程序结束后由系统释放函数

  2. 常量区: 常量区用来存放常量字符串等,程序结束后由系统释放工具

  3. 静态区:全局变量和静态变量的存储是放在一块的,程序结束后由系统释放布局

  • 初始化的全局变量和静态变量在一块区域,.data。
  • 未初始化的全局变量和未初始化的静态变量在相邻的另外一块区域,.bSS。

    全局变量和静态变量要尽可能少用。由于这些变量在程序的生命周期中不会变释放,比较容易占用内存空间,不适合存储比较大量的数据post

  1. 堆区(heap)
  • 堆区的地址是从低到高分配,因此先声明的变量地址要比后声明的变量地址小
  • 通常由程序员分(new、malloc)释放,若程序员不释放,程序结束时可能由操做系统回收
  • 堆是向高地址扩展的数据结构,,是一块不连续的内存的区域。引文系统是用链表来存储的空闲内存地址的。
  • 在ios中,堆区的内存是应用程序共享的,堆中的内存分配是系统负责
  1. 栈区(stack)
  • 存放函数的参数值、局部变量的值等,由编译器自动分配释放,一般在函数执行结束后就释放了,iOS中栈区的大小是2M。
  • 栈上的地址是从高到低分配,先声明的变量地址比后声明的变量地址要大。
  • 栈是向低地址扩展的数据结构,是一块连续的内存的区域

堆空间的申请

  1. 操做系统有一个记录空闲内存地址的链表。
  2. 当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,而后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。
  3. 因为找到的堆结点的大小不必定正好等于申请的大小,系统会自动的将多余的那部分从新放入空闲链表中

栈空间的申请

每个函数在执行的时候都会向操做系统索要资源,栈区就是函数运行时的内存,栈区中的变量由编译器负责分配和释放,内存随着函数的运行分配,随着函数的结束而释放,由系统自动完成。测试

  • 静态分配是编译器完成的,好比自动变量(auto)的分配。
  • 动态分配由alloca函数完成。栈的动态分配无需释放(是自动的),也就没有释放函数

堆栈的区别

  • 按管理方式分
  1. 对于栈来说,是由系统编译器自动管理,不须要程序员手动管理
  2. 对于堆来说,释放工做由程序员手动管理,不及时回收容易产生内存泄露
  • 按分配方式分
  1. 堆是动态分配和回收内存的,没有静态分配的堆
  2. 栈有两种分配方式:静态分配和动态分配
    • 静态分配是系统编译器完成的,好比局部变量的分配
    • 动态分配是有alloc函数进行分配的,可是栈的动态分配和堆是不一样的,它的动态分配也由系统编译器进行释放,不须要程序员手动管理

小结

我的以为主要弄清楚如下几点

  1. 内存分配的地址从低到高,仍是从高到低
  2. 每一个段或者区存放的变量是什么类型的
  3. 栈和堆的管理、申请、释放、以及二者区别等,栈和堆是一个重点

demo示例【很重要】

运行环境:选择的模拟器是Iphone11 Pro Max

static int a = 10;
static int b;

- (void)viewDidLoad {
    [super viewDidLoad];
       /* 代码段 */
    IMP imp = method_getImplementation(class_getInstanceMethod(self.class, @selector(viewDidLoad)));
    NSLog(@"【代码段】==> 编译以后的函数");
    NSLog(@"imp --- %p", imp);

    /* 数据段 */
    static int c = 10;
    static int d;
     /* 常量区 */
    NSString *str1 = @"脚底按摩"; // 直接写出来的,不是经过方法建立的字符串,编译时会生成为【字符串常量】
    NSString *str2 = @"精油推背";
    NSLog(@"【数据段/常量区】==> 字符串常量");
    NSLog(@"str1 --- %p", str1);
    NSLog(@"str2 --- %p", str2);
    /* 静态初始化区 */
    NSLog(@"【数据段/静态区】==> 已初始化数据");
    NSLog(@"c ------ %p", &c);
    NSLog(@"a ------ %p", &a);
     /* 静态未初始化区 */
    NSLog(@"【数据段/静态区】==> 未初始化数据");
    NSLog(@"d ------ %p", &d);
    NSLog(@"b ------ %p", &b);
    /* 堆 */
    NSObject *obj = [[NSObject alloc] init];
    NSString *str3 = [NSString stringWithFormat:@"%@", @"测试字符串是否在堆上"];
    NSLog(@"【堆】==> 实例对象"); // 分配的内存空间地址【愈来愈大】,不连续的
    NSLog(@"obj ---- %p", obj);
    NSLog(@"str3 --- %p", str3);
    /* 栈 */
    int e = 20;
    int f;
    NSLog(@"【栈】==> 局部变量"); // 分配的内存空间地址【愈来愈小】,是连续的,无论有没有初始化都会分配
    NSLog(@"e ------ %p", &e);
    NSLog(@"f ------ %p", &f);
}
// 打印结果
2020-03-24 23:05:05.707713+0800 03-内存管理-内存布局[3812:249391] 【代码段】==> 编译以后的函数
2020-03-24 23:05:05.707854+0800 03-内存管理-内存布局[3812:249391] imp --- 0x10f55cae0
2020-03-24 23:05:05.707985+0800 03-内存管理-内存布局[3812:249391] 【数据段/常量区】==> 字符串常量
2020-03-24 23:05:05.708091+0800 03-内存管理-内存布局[3812:249391] str1 --- 0x10f55f060
2020-03-24 23:05:05.708197+0800 03-内存管理-内存布局[3812:249391] str2 --- 0x10f55f080
2020-03-24 23:05:05.708307+0800 03-内存管理-内存布局[3812:249391] 【数据段/静态区】==> 已初始化数据
2020-03-24 23:05:05.708411+0800 03-内存管理-内存布局[3812:249391] c ------ 0x10f5614c0
2020-03-24 23:05:05.708510+0800 03-内存管理-内存布局[3812:249391] a ------ 0x10f5614c4
2020-03-24 23:05:05.708621+0800 03-内存管理-内存布局[3812:249391] 【数据段/静态区】==> 未初始化数据
2020-03-24 23:05:05.708723+0800 03-内存管理-内存布局[3812:249391] d ------ 0x10f561648
2020-03-24 23:05:05.708982+0800 03-内存管理-内存布局[3812:249391] b ------ 0x10f56164c
2020-03-24 23:05:05.719411+0800 03-内存管理-内存布局[3812:249391] 【堆】==> 实例对象
2020-03-24 23:05:05.719575+0800 03-内存管理-内存布局[3812:249391] obj ---- 0x600002a05610
2020-03-24 23:05:05.719692+0800 03-内存管理-内存布局[3812:249391] str3 --- 0x600002642400
2020-03-24 23:05:05.719810+0800 03-内存管理-内存布局[3812:249391] 【栈】==> 局部变量
2020-03-24 23:05:05.719932+0800 03-内存管理-内存布局[3812:249391] e ------ 0x7ffee06a10c4
2020-03-24 23:05:05.720063+0800 03-内存管理-内存布局[3812:249391] f ------ 0x7ffee06a10c0
复制代码

总结

  1. 栈的地址通常是0x7开始,堆是0x6开始,而数据段是0x1
  2. 静态区是在同一段内存连续分配的,按内存地址增加方向分配。
  3. 未初始化静态区的地址比初始化的静态区地址更大,
  4. 栈空间也是同一段内存连续分配的,按内存地址减少方向分配

问题和探讨

  • ❓❓❓堆区的地址是从低到高分配,先声明的变量地址要比后声明的变量地址小,可是demo的输出结果,倒是从高到低分配的,这个还有待进一步了解
NSObject *obj = [[NSObject alloc] init];
NSString *str3 = [NSString stringWithFormat:@"%@", @"测试字符串是否在堆上"];
2020-03-24 23:05:05.719411+0800 03-内存管理-内存布局[3812:249391] 【堆】==> 实例对象
2020-03-24 23:05:05.719575+0800 03-内存管理-内存布局[3812:249391] obj ---- 0x600002a05610
2020-03-24 23:05:05.719692+0800 03-内存管理-内存布局[3812:249391] str3 --- 0x600002642400
复制代码
相关文章
相关标签/搜索