iOS 中内存分配与分区及为什么要区分堆栈

关于RAM ROM

关于RAM ROM RAM与ROM就是具体的存储空间,统称为存储器

  • RAM(random access memory):运行内存,CPU可以直接访问,读写速度非常快,但是不能掉电存储。它又分为:
    • 动态DRAM,速度慢一点,需要定期的刷新(充电),我们常说的内存条就是指它,价格会稍低一点,手机中的运行内存也是指它
    • 静态SRAM,速度快,我们常说的一级缓存,二级缓存就是指它,当然价格高一点。
  • ROM(read only memory):存储性内存,可以掉电存储,例如SD卡、Flash(机械磁盘也可以简单的理解为ROM)。用的多的:NandFlash,还有NorFlash,现在用的已经比较少了(两者主要区别是前者空间大,便宜,后者可以直接运行程序,读取速度快)
    由于RAM类型不具备掉电存储能力(即一停止供电数据全没了,从新上电后全是乱码,所以需要初始化),所以app程序一般存放于ROM中。RAM的访问速度要远高于ROM,价格也要高。
RAM与ROM协同工作

由于RAM不能掉电存储,所以我们的APP程序,刷机包,下载的文件等等,都是在ROM里面存储的。
手机里面使用的ROM基本都是NandFlash,CPU是不能直接访问的,而是需要文件系统/驱动程序(嵌入式中的EMC)将其读到RAM里面,CPU才可以访问。另外,RAM的速度也比NandFlash快。

内存分区:可以分为5个区

说到内存分区,内存即指的是RAM

  • 栈区(stack): 这个一般由编译器操作,或者说是系统管理,会存一些局部变量,函数跳转跳转时现场保护(寄存器值保存于恢复),这些系统都会帮我们自动实现,无需我们干预。 所以大量的局部变量,深递归,函数循环调用都可能耗尽栈内存而造成程序崩溃

  • 堆区(heap): 一般由程序员管理,比如alloc申请内存,free释放内存。我们创建的对象也都放在这里

  • 全局区(静态区 static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放。注意:在嵌入式系统中全局区又可分为未初始化全局区:.bss段和初始化全局区:data段。举例:int a;未初始化的。int a = 10;已初始化的。

  • 常量区:常量字符串就是放在这里的,还有const常量

  • 代码区:存放代码,app程序会拷贝到这里,程序不是在ROM里面存储吗?看下面的举例
    ]

图中各个区并不连续

程序运行举例(CPU RAM ROM之间协同)

首先了解下:虚拟内存与物理内存

手机上的所有程序都是依托操作系统,运行在虚拟内存上的,每一个APP都会以为自己拥有所有的虚拟内存。比如一个手机,它是32位操作系统(一般也是32位总线),真实的物理内存为2G:

那么他的寻址空间为4G(2的32次方),对于APP来说,它觉得自己拥有4G的内存,虽然这是不可能的(或者说同一时间是不可能的),但是,操作系统只要保证APP当时用到的地址空间有真实的物理地址对应就可以,APP也不需要知道那对应的2G真实物理内存具体在哪里。不要求4G的虚拟内存同一时间都有真实的物理内存相对应,当然那也是不可能的,因为只有2G物理内存

在下面的举例中,只考虑虚拟内存

当我们点击手机屏幕APP的Icon启动一个APP(例如微信)时
操作系统会为微信开辟4G的虚拟内存空间(开辟真实的物理内存,对应一部分到4G的虚拟内存)
操作系统会把存储在ROM里面微信的部分代码(受空间所限,不可能全部拷贝),拷贝到上一步开辟的4G内存空间的代码区,如上图
然后CPU就可以访问RAM来运行微信的程序了
假设通过微信我们下载了一个100M的视频,那么会从服务器一点一点的下载到RAM,然后再从RAM写到ROM存储。这样才能保证,我们关掉微信并再次打开时视频还在

假设隔一段时间,我们要看视频,程序会将它从ROM读到RAM然后解码播放

编程注意

当一个app启动后,代码区,常量区,全局区地址已固定,因此指向这些区的指针不会为空而产生崩溃性的错误。而堆区和栈区是时时刻刻变化的(堆的创建销毁,栈的弹入弹出),所以当使用一个指针指向这两个区里面的内存时,一定要注意内存是否已经被释放,否则会产生程序崩溃(编程中很常见)。
原文:https://www.cnblogs.com/mddblog/p/4405165.html

为什么区分堆栈

回顾下

  • 栈区(stack): 这个一般由编译器操作,或者说是系统管理,会存一些局部变量,函数跳转跳转时现场保护(寄存器值保存于恢复),这些系统都会帮我们自动实现,无需我们干预。 所以大量的局部变量,深递归,函数循环调用都可能耗尽栈内存而造成程序崩溃
  • 堆区(heap): 一般由程序员管理,比如alloc申请内存,free释放内存。我们创建的对象也都放在这里

堆栈有什么结构差异?

栈让你分配和释放简单化,但是他有一个重大缺点:释放的次序是固定的,必须是分配次序的反序。
假设栈上对象分配次序是ABCDEF。 可能在某些情况时,我释放F的时候,A可以释放了但其他对象都不能释放;某些情况时我释放F的时候,B可以释放了但其他对象都不能释放… 对不起 这些栈上对象都不能提前释放:ABCD必须等待F和E释放了之后才能释放。
换而言之,栈的分配和释放非常迅速(一个函数内栈的分配和回收 各自只需要一条指令),但是释放非常不灵活,极易造成浪费。越是复杂的程序,函数调用栈越深的情况,你将越会频繁的遇到这种函数A call 函数B,B知道A的某个局部变量可以提前回收内存了但是却无能为力除非B返回的场景。
所以,操作系统api都会限制栈的最大大小来让你浪费有个上限度,并且提供了“堆”。事实上,在栈出来之前,所有的内存都是堆,所有的内存都是供程序员自由的分配和释放。然而堆的管理显然复杂的多,因为栈的已分配和未分配内存各自是连续的一块,堆却是很多块已分配和未分配的内存混杂在一起,需要程序员去在这些内存块里标记他们的长度,分配时选择或者分割一个合适的块,回收时合理的进行合并。所以高级语言出来把栈和堆分开之后,第一件事就是得封装堆,glibc就是干这事。
事实上,即使在堆上面,现代化的高级语言也会有一些桎梏让你并不能随意的,随时随地的释放你认为可以释放的内存,很多语法概念中都蕴含了“栈”的概念。
例如,一个C++的对象X,他有数据成员a和b, 现在我知道我不再使用X.a了,但是我还会使用X.b,有没有办法去“浅释放”X.a所占的sizeof(X.a)的内存呢?抱歉不行,你只能先释放X,在释放X的过程中去释放X.a和X.b(不能只释放一个)。而且这和X在堆还是在栈上分配没有任何关系。
换而言之,面向对象语言中的“对象”,从他的成员的构建过程中蕴含了“栈的概念”,所以他的释放依然受制于栈的先入后出的规则影响。类似于默认生成的析构函数总是先调用对象的析构函数再去遍历调用成员的析构函数一样,只是可以把这个栈看成分叉栈,或者树的先序遍历。
无垃圾回收的函数语言也不例外:函数表达式更是一个栈展开&回卷的过程,函数返回值这块内存(那些喜欢掉书袋的“纯函数语言”只有函数返回值这唯一一个显式内存分配机会)的回收机理依然受制于栈的先入后出的规则影响。而且这和返回值是存储在堆还是在栈上没有太多的关系,在堆上只可能让内存回收更加延迟的(如果编译器实现的不好)。
链接:https://www.zhihu.com/question/281940376/answer/425123578

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值