专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

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

内存管理系列文章:

前言

OC底层实现其实都是C语言的代码,所以想深入理解iOS的内存管理机制,可以通过了解C语言的内存管理来进一步熟悉OC的内存管理。

内存分类

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

RAM和ROM的区别

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入口函数开始执行代码,在执行代码的过程中,会创建对象和一些局部变量,其中对象存放在堆中,变量存放在栈上。

内存分区

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

75_1.png

区段解释

1、 程序代码区:代码区用来存放函数体的二进制代码,程序结束后由系统释放
2、 常量区: 常量区用来存放常量字符串等,程序结束后由系统释放
3、 静态区:全局变量和静态变量的存储是放在一块的,程序结束后由系统释放

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

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

1、 堆区(heap)

  • 堆区的地址是从低到高分配,所以先声明的变量地址要比后声明的变量地址小
  • 一般由程序员分(new、malloc)释放,若程序员不释放,程序结束时可能由操作系统回收
  • 堆是向高地址扩展的数据结构,,是一块不连续的内存的区域。引文系统是用链表来存储的空闲内存地址的。
  • 在ios中,堆区的内存是应用程序共享的,堆中的内存分配是系统负责

1、 栈区(stack)

  • 存放函数的参数值、局部变量的值等,由编译器自动分配释放,通常在函数执行结束后就释放了,iOS中栈区的大小是2M。
  • 栈上的地址是从高到低分配,先声明的变量地址比后声明的变量地址要大。
  • 栈是向低地址扩展的数据结构,是一块连续的内存的区域 75_2.png

堆空间的申请

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

75_3.png

文章永久链接:https://tech.souyunku.com/31641

未经允许不得转载:搜云库技术团队 » 内存管理系列从C语言看OC的内存布局

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们