NSSting内存
- 栈区(stack): 这个一般由编译器操作,或者说是系统管理,会存一些局部变量,函数跳转跳转时现场保护(寄存器值保存于恢复),这些系统都会帮我们自动实现,无需我们干预。 所以大量的局部变量,深递归,函数循环调用都可能耗尽栈内存而造成程序崩溃
- 堆区(heap): 一般由程序员管理,比如alloc申请内存,free释放内存。我们创建的对象也都放在这里
- 全局区(静态区 static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放。注意:在嵌入式系统中全局区又可分为未初始化全局区:.bss段 和初始化全局区:data段。举例:int a;未初始化的。int a = 10;已初始化的。
- 常量区:常量字符串就是放在这里的,还有const常量
- 代码区:存放代码,app程序会拷贝到这里
__NSCFConstantString显然是常量字符串,地址0x10c843820自然就是存储在常量区。
__NSCFString表示为oc对象,NSString就是封装的CFString,0x6000000315c0地址显示这个字符串对象存储在堆中。
NSTaggedPointerString这个类表示这是字符串的一种指针Tagged Pointer,0xa636261646362617这个地址为什么如此与众不同呢,接下来我们就简单介绍这钟字符串的存储指针。
在苹果推出了 采用64位架构的A7双核处理器 iphone 5s的时候,为了节省内存和提高执行效率,苹果提出了Tagged Pointer的概念。先看看原有的对象为什么会浪费内存。假设要存储一个 NSNumber 对象,其值是一个整数。正常情况下,如果这个整数只是一个 NSInteger 的普通变量,那么它所占用的内存是与CPU的位数有关,在32位CPU下占4个字节,在64位CPU下是占8个字节的。而指针类型的大小通常也是与CPU位数相关,一个指针所占用的内存在32位CPU下为4个字节,在64位CPU下也是8个字节。所以一个普通的iOS程序,如果没有Tagged Pointer对象,从32位机器迁移到64位机器中后,虽然逻辑没有任何变化,但这种NSNumber、NSDate一类的对象所占用的内存会翻倍。
对于以前的@""符号创建的字符串还是常量字符串,这个没有改变,但是采用stringWithFormat 等NSString方法的创建字符串对象则有了区别。
在苹果的64位OC实现中,若对象指针的二进制第一位是1,则该指针为Tagged Pointer。
例如0xa000000000000311其中a的2进制为1010,第一位1表示这是Tagged Pointer,010表示这是一个NSTaggedPointerString类;这个地址最后一位表示字符串的数目,这里是0001表示有1位字符串;其中真正用来存储的位数只有中间的14位16进制。这个地址本身其实就存储了字符串的值,可以说是存储在&strS内存中值,只是伪装成了地址,它不需要存储在数据区,也不需要申请堆空间。
NSTaggedPointerString的存储有三种编码方式:ASCII码,六位编码,五位编码。
ASCII码
我们发现NSTaggedPointerString存储内容除去第一位和最后一位,其实只有中间的14位16进制字符,再看ascll码由8位二进制组成,所以这里(14*4) / 8=7,用8位的ascll码的话最多可以存储7个字符。字符串数目0~7之间
[NSString stringWithFormat:@"1"]输出的地址 0xa000000000000311,其中31的2进制是0011 0001,在ascll码表里查找发现正是对应着“1”;
六位编码:
NSTaggedPointerString 采用六位二进制编码,(14*4)/6=9.333…,可以看出最多存储9位字符。字符数目8~9
五位编码:
采用五位二进制编码,(14*4)/5 = 11.2,可以看出这种编码最多存储11位字符。字符数目在10~11
NSTaggedPointerString 存储编码中的六位和五位编码都是根据通常代码中字母使用频率来排序的,但并不是一成不变的,apple会持续更新并统计字母使用频率,系统每次升级都可能不一样,当前第一位是字母e,之后是i,l,o,t…;这两种编码是从左向右的;根据编码位数我们显然也能推测出并不是所有字符都可以进行ascll或者六位五位编码的,当出现这样不能编码的时候,系统也就不会使用NSTaggedPointerString类。