33.iOS底层学习之内存管理TaggedPointer

本篇提纲
1、内存五大区
2、TaggedPointer
3、Retain和Release对于小对象类型的处理

1.内存五大区

  • 堆区(Heap)
    堆区在运行时分配,一般由程序员分配和释放,若程序员不释放,程序结束后可能由OS回收,分配方式类似于链表,存储方式是不连续的,它的效率不如栈。
    iOS中堆区的地址一般是以0x6开头。
    OC中,使用alloc或者new来创建对象,在ARC下由系统回收释放。
    C语言中,使用malloccallocrealloc开辟的空间,需要手动调用free函数进行释放。
    堆区的内存空间较大,可以存储整个结构体以及成员变量。

  • 栈区(Stack)
    栈是系统数据结构,栈所对应的进程或者线程是唯一的。
    栈的地址由高到底,是一块连续的内存区域,遵循先进后出原则。
    在iOS中,一般以0x7开头,在寄存器sp中。
    栈区由系统负责分配和销毁,效率高,速度快。
    栈区中存储局部变量和方法/函数的参数
    iOS主线程栈的大小为1MB,其他线程为512k

  • 全局静态区( .bss和 .data)
    全局区在编译时分配内存空间,在iOS中一般以0x1开头。
    全局区的数据一直存在,程序结束后由系统释放
    全局区存储全局变量和static修饰的静态变量,其中静态变量分为全局静态变量和静态局部变量。
    未初始化的变量存储在BSS区(.bss),已经初始化的存储在数据区(.data)

  • 常量区(.rodata)
    常量区在编译时分配。
    常量区的数据会一直存在,程序结束后由系统释放。

  • 代码段(.text)
    代码区在编译时分配。
    代码区存储程序运行时的代码,会被编译成二进制存储进内存。

2.TaggedPointer

2.1 TaggedPointer的引入

从2013年苹果推出了iPhone5s之后,iOS的内存寻址空间扩大到了64位。我们可以用63位表示一个数字。那么这个数字的范围是2^63,是一个很大的数字,一般情况我们用不到这么大的数字,所以使用63位来存储一个数字对于空间是非常浪费的。苹果为了优化这一个问题,引入了Tagged Pointer

Tagged Pointer是一种特殊的指针,它特殊在于它存储的不是地址,而是真实的数据和一些附加的信息。

Tagged Pointer专门用来存储小对象,例如:NSNumberNSDateNSString

Tagged Pointer指针的值不再是地址了,而是真正的值,所以不是一个对象,它的内存不是在堆中,不需要malloc和free。在内存读取上有着3倍的效率,创建时比以前快106倍。

2.2 TaggedPointer的例子
字符串打印

通过打印我们可以看到,打印出来三个类型的字符串:NSTaggedPointerString__NSCFConstantString__NSCFString。我查了下相关资料关于这三种字符串的区别。

  • NSTaggedPointerString
    小对象类型,存储在栈区,通过方法 NSStringFromClass创建的,字符串长度不超过10个的时候,字符串的类型为NSTaggedPointerString

  • __NSCFConstantString
    常量字符串,存储在常量区,通过方法initWithString创建,或者直接创建,此时类型是__NSCFConstantString

  • __NSCFString
    可变字符串,存储在堆区,通过方法[NSMutableString alloc]initWithString或者NSStringFromClass长度超过10的字符串为__NSCFString类型。

2.3 TaggedPointer源码相关

通过objc4_818.2的源码中可以看到关于TaggedPointer的一些方法。

  • TaggedPointer的编码
    通过查找TaggedPointer相关,可以找到关于操作TaggedPointer的一系列方法。
    _objc_encodeTaggedPointer
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
    uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);
#if OBJC_SPLIT_TAGGED_POINTERS
    if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
        return (void *)ptr;
    uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
    uintptr_t permutedTag = _objc_basicTagToObfuscatedTag(basicTag);
    value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
    value |= permutedTag << _OBJC_TAG_INDEX_SHIFT;
#endif
    return (void *)value;
}

首先看到value是通过objc_debug_taggedpointer_obfuscatorptr进行异或得到,而objc_debug_taggedpointer_obfuscator的值,在支持TaggedPointer的情况下是通过方法initializeTaggedPointerObfuscator进行初始化的。

Initialize objc_debug_taggedpointer_obfuscator with randomness.

/***********************************************************************
* initializeTaggedPointerObfuscator
* Initialize objc_debug_taggedpointer_obfuscator with randomness.
*
* The tagged pointer obfuscator is intended to make it more difficult
* for an attacker to construct a particular object as a tagged pointer,
* in the presence of a buffer overflow or other write control over some
* memory. The obfuscator is XORed with the tagged pointers when setting
* or retrieving payload values. They are filled with randomness on first
* use.
**********************************************************************/
static void
initializeTaggedPointerObfuscator(void)
{
    if (!DisableTaggedPointerObfuscation) {
        // Pull random data into the variable, then shift away all non-payload bits.
        arc4random_buf(&objc_debug_taggedpointer_obfuscator,
                       sizeof(objc_debug_taggedpointer_obfuscator));
        objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;

#if OBJC_SPLIT_TAGGED_POINTERS
        // The obfuscator doesn't apply to any of the extended tag mask or the no-obfuscation bit.
        objc_debug_taggedpointer_obfuscator &= ~(_OBJC_TAG_EXT_MASK | _OBJC_TAG_NO_OBFUSCATION_MASK);

        // Shuffle the first seven entries of the tag permutator.
        int max = 7;
        for (int i = max - 1; i >= 0; i--) {
            int target = arc4random_uniform(i + 1);
            swap(objc_debug_tag60_permutations[I],
                 objc_debug_tag60_permutations[target]);
        }
#endif
    } else {
        // Set the obfuscator to zero for apps linked against older SDKs,
        // in case they're relying on the tagged pointer representation.
        objc_debug_taggedpointer_obfuscator = 0;
    }
}

这个方法通过DisableTaggedPointerObfuscation来判断是否开启了混淆。
开启了objc_debug_taggedpointer_obfuscator的值是一个随机数;未开启直接赋值为0。

所以,方法_objc_encodeTaggedPointer的编码就是当前的值和随机数进行一个异或操作。我们再来看看解码操作。

  • TaggedPointer的解码
static inline uintptr_t
_objc_decodeTaggedPointer_noPermute(const void * _Nullable ptr)
{
    uintptr_t value = (uintptr_t)ptr;
#if OBJC_SPLIT_TAGGED_POINTERS
    if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
        return value;
#endif
    return value ^ objc_debug_taggedpointer_obfuscator;
}

static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
    uintptr_t value = _objc_decodeTaggedPointer_noPermute(ptr);
#if OBJC_SPLIT_TAGGED_POINTERS
    uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;

    value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
    value |= _objc_obfuscatedTagToBasicTag(basicTag) << _OBJC_TAG_INDEX_SHIFT;
#endif
    return value;
}

方法_objc_decodeTaggedPointer通过调用_objc_decodeTaggedPointer_noPermute方法,而_objc_decodeTaggedPointer_noPermute的实现就是,通过这个objc_debug_taggedpointer_obfuscator和当前的value进行再异或就完成了解码。这里我们以32位机器来复习下异或运算:

  • A异或0xffff ffff = A的取反
  • A异或0x0000 0000 = A
  • A异或B再异或一次B = A

所以上面这个TaggedPointer类型的编码和解码,都是通过异或运算来完成的。编码时c = A异或objc_debug_taggedpointer_obfuscator;
解码时 A = c异或objc_debug_taggedpointer_obfuscator;

我们也可以通过他这个解码的规律在程序里进行解码操作试试解出TaggedPointer中存储的内容。

2.4 TaggedPointer解码实例操作

首先在代码中自定义一个解码的方法:

extern uintptr_t objc_debug_taggedpointer_obfuscator;
uintptr_t kc_objc_decodeTaggedPointer(id ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}

然后分别去解码NSStringNSNumberNSIndexPathNSDate

  • 2.4.1解码NSString
- (void)indexPathDemo{
    NSString *str = [NSString stringWithFormat:@"Luck"];
    NSLog(@"%p-%@-%@ - 0x%lx",str,str,str.class,kc_objc_decodeTaggedPointer(str));
}

输出结果:
0xa0000006b63754c4-Luck-NSTaggedPointerString - 0xa0000006b63754c4
使用模拟器运行,解码的结果为0xa0000006b63754c4我们通过lldb,将其转化为二进制:

转化为二进制

首先0b表示二进制。
然后b后面的第一位为1,表示该isaTagged Pointer类型。
第二到四位标记了TaggedPointer的指针类型,010,表示字符串类型,下面附上类型的定义。
解码的最后四位表示长度是100也就是长度为4,后面可以验证下,逐一加长看最后四位的变化。
那么剩下的就表示真实的内容。

TaggedPointer的指针类型

三位的范围是000 ~ 111也就是0~7,能表示对应的这八种类型。
接下来我们来解析下实质的内容把LuckTaggedPointer解出来。

内容解析再看二进制就比较费劲了,因为数字比较大,所以我们还是来看十六进制这串0xa0000006b63754c4
前边已经说过第一位是表示是不是小对象类型,这个首位的a转成二进制是1010,也就对应了那个小对象类型和字符串,而最末尾的4表示长度,剩下的有效内容就是6b 63 75 4c转成十进制就是107 99 117 76

ASCII码表

再来对应下相应的ASCII码表,分别查出这几个对应的ASCII值,107->k,99->c,117->u,76->L。因为存储方式是小端模式,所以高位存到低地址,反读就是Luck,就拿到了我们存储的内容。我们按照这个方式再来验证一下。

例子:
如果我们这一次去存字符串ABCDE,那么我们通过上面的推导进行反推导打印出来的结果。
首先ABCDE对应的ASCII分别是65 66 67 68 69转成16进制为41 42 43 44 45
小端模式存储反着存为 45 44 43 42 41
长度是5,末尾加个5变成45 44 43 42 41 5
类型是小对象字符串,所以前四位也是1010十六进制就是a
所以最终打印出来的结果是0xa000045444342415这样的!
我们运行一下来验证下这个规律。

ABCDE结果


此处微微一笑!也想像KC一样问问还有谁(实在是太洗脑了这个台词😂😂😂)!!

微微一笑
  • 解码NSNumber
    首先关闭数据混淆。


    模拟器

可以看到直接就是存的值1,末尾表示类型:
0 -> char
1 -> short
2 -> int
3 -> long
4 -> float
5-> double

而真机运行情况会有所变化:

真机情况

最后三位变成了类型标记小对象类型011对应的是3也就是NSNumber
第四位到第七位表示的是具体的类型同上000000010010001101000101对应也是0~5这几个类型。

3.Retain和Release对于小对象类型的处理

直接看源码关于RetainRelease对于小对象类型的处理。

  • Retain
    找到方法rootRetain

    处理小对象类型

    可以看到小对象类型不进行引用计数的内存管理操作。

  • Release


    小对象类型的Release

    同样对应的小对象类型不进行release的操作!

总结

本篇文章主要着重介绍到了小对象类型Tagged Pointer的一些概念和相关的理解,它的作用,带来的优化等等。下一篇继续针对内存管理进行更加深入的相关知识SideTable的学习。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容