最近准备面试基础知识被问麻了;于是研讨了一些比较抢手的知识点,其中一个便是TaggedPointer;之前有所耳闻,这玩意不存地址,直接存值;详细什么原理没有了解过,前几天细心研讨了一下。

这儿只针对NSString的TaggedPointer;由于一般数字和地址都是数字,小的数字就正常按值写到指针的位上去应该就能够了,只要字符的TaggedPointer需求表明成ASCII的时分,这个规矩我研讨一圈下来感觉有点怪,这儿解释了一下研讨进程,结尾提几个自己的疑问点,在网上搜了一下,也问了下GPT,没能找到合理的解。

下面便是详细步骤

假如需求写Demo检查TaggedPointer细节的话,需求在项目schema中设置一下,加一个装备项:OBJC_DISABLE_TAG_OBFUSCATION = YES;不加这个装备的话,打印出来的TaggedPointer的值是乱码,找不到规矩。

写Demo的时分,我写的都是一些有规矩的字符串,比较容易在二进制里面找到一些规矩,至少能够经过重复的子串看怎样分段的,所以我先找一些循环的数字字符串如:@”121212″,用%p打印一下地址;为了找规矩清晰一些我先连写几个字符串的地址:

例1:taggedPointer 6位字符

121212 0x80191899189918b2

1000 0000 0001 1001 0001 1000 1001 1001 0001 1000 1001 1001 0001 1000 1011 0010

例2:taggedPointer 6位字符

123123 0x80199918999918b2

1000 0000 0001 1001 1001 1001 0001 1000 1001 1001 1001 1001 0001 1000 1011 0010

例3:非TaggedPointer,一般地址

123123123123 0x600001c49e20

0110 0000 0000 0000 0000 0001 1100 0100 1001 1110 0010 0000

例4:TaggedPointer,7位字符

1231231 0x98999918999918ba

1001 1000 1001 1001 1001 1001 0001 1000 1001 1001 1001 1001 0001 1000 1011 1010

例5:TaggedPointer, 9位

123123123 0x8f50fbd43ef50fca

1000 1111 1001 0000 1111 1011 1101 0100 0011 1110 1111 0101 0000 1111 1100 1010

例6:taggedPointer,8位:

12312312 0x803d43ef50fbd442

1000 0000 0011 1101 0100 0011 1110 1111 0101 0000 1111 1011 1011 0100 0100 0010

前缀:

TaggedPointer的时分榜首位总是1,这个我在别的文章中看到过,有个符号位来区分他和一般地址(msgSend也会用这个符号位决定怎样发消息,由于这种对象没有isa指针需求用特殊的办法发消息)。这个数符号它是不是TaggedPointer,16进制地址的话,假如榜首个数是大于8的,应该便是个TaggedPointer。

后缀:

然后发现最终三位总是010,这三位看起来没啥用,也区分不了啥,我也不知道干啥的,就先忽略了。

再看看例6和例5:

12312312 0x803d43ef50fbd442

1000 0000 0011 1101 0100 0011 1110 1111 0101 0000 1111 1011 1011 0100 0100 0010

123123123 0x8f50fbd43ef50fca

1000 1111 1001 0000 1111 1011 1101 0100 0011 1110 1111 0101 0000 1111 1100 1010

倒数第二格相同,最终一格最前面多了个1;字符个数也是多了一个,所以最终一格的榜首位应该是表明字符个数位的最终一位;同样也能够经过[比如4、比如2]、[比如4、比如5]验证;字符多1个的时分后缀是怎样变的,规矩共同,不可能这么巧都是这样,所以末尾便是表明字符个数的,详细便是倒数第四位到倒数第七位都是字符个数。

中心:

7位及以下:

看下例5和例6,8位的时分前面多了一群0,8位从第二格就开始全占满了,估量是8位的时分前面的没用,所以推测这个数字都是从后往前排的,从倒数第8位往前排;假如9位的话能用到榜首格,8位就没用到;不过你可能会有疑问6、7位的时分,第二格也占满了,这个后边说,现在便是定下来优先占后边的位的就行了;不信也能够写个很短的”12″:

12 0x8000000000191892

直接看出来,前面一堆0;所以必定是优先占后边的位,前面都没用到,全0;就直接用这个比如看看详细怎样表明的,写出二进制:

1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 1001 0001 1000 1001 0010

跟前面相同,最终面7个是个数:0010 = 2表明2个字符,加一个 010不知道干啥的;前面一堆0也都不要了,剩余:1100 1000 1100 01, 大致看一下1和2的ASCII,”1“是49,”2″是50,没差多少,表明成二进制的话,前面应该都相同,后边差个1;大致拆两头看的话,前面两个11的应该都是高位数,所以就拆:

11001000

110001

前面结尾的俩0去掉,110010 ,后边110001,前面32 + 16 + 2 = 50,后边32 + 16 + 1 = 49;便是2和1的ASCII。我用的字符是12,他排的时分是排的21,中心空了00,不知道为什么空;然后找个长点的验证一下。

验证7以下的摆放:

1231231

先写前缀:1000

再写后缀:7个字符 0011 1010(便是7 + 010)

然后中心的倒序从后往前排,1 的 ASCII: 110001 ; 2的 110010 ;3的110011 (ASCII是接连的记住1的49,后边往后一个一个数就行);早年往后默写,遇到什么数把什么数复制下来:

1321321(之前12的时分说了是倒序的):

写成了:

110001 00 110011 00 110010 00 110001 00 110011 00 110010 00 110001

一共是 7*6 + 6 * 2 = 54位,后边7位,前面4位,一共是65位,超了,咋整:后缀表明数字的必定改不了,前面1000,后边3个0也不知道干啥的,直接往前面顶掉一个0,横竖也没用,先整着呗:

(100) + (110001 00 110011 00 110010 00 110001 00 110011 00 110010 00 110001 ) + ( 011 1010)

拆成4个一格:
1001 1000 1001 1001 1001 1001 0001 1000 1001 1001 1001 1001 0001 1000 1011 1010

都写成16进制:

98999918999918ba

1231231 0x98999918999918ba

下面这个我直接抄下来的,一模相同;我这一看那指定前面就都对了。可是中心把前缀顶掉了一个0,所以从范围上看便是前缀3位,后缀7位,中心剩54位都是表明字符的ASCII;两个字符之距离着2个00。

八位及以上:

到这,又有个疑问:上面7位就写满了,那8/9个字符的时分那还不得超了啊,可是试的时分8/9个字符的时分的确都是TaggedPointer,那就直接用上面8个字符的比如看看,规矩应该不相同:

12312312 0x803d43ef50fbd442

1000 0000 0011 1101 0100 0011 1110 1111 0101 0000 1111 1011 1011 0100 0100 0010

它甚至都没用满,先把前后缀拿出来,这都是有用的符号不占字符:

(100) 0 0000 0011 1101 0100 0011 1110 1111 0101 0000 1111 1011 1011 0100 0(1000010)
不考虑前后缀,只看中心的,最终6格(1 0100 0) 指定是个数,依照前面的推论:不管他是啥,前面两个应该是00距离的;可是它前面是个1;这就必定了8位及以上的规矩和8位以下的规矩不相同。所以谨慎推测前面的6位必定也是个数;这个怎样验证呢:由于我用的12312312,每3个一循环,从最终的6位最初,再往前找18位,以那个最初的6位数假如和当前这个6位一模相同,那必定就对了,有距离的话,必定是大于18位一循环的,没有距离便是刚好18位一循环,我这儿按6个一分,分了一下:

0 0000 0 (011 110) (1 0100 0) (011 111) (0 1111 0) (101 000) (0 1111 1) (011 101) (1 0100 0)

倒数第四个括号和倒数榜首个括号,里面装的相同的;那就必定对了,两个字符之间没有00。一括号便是一个数,不信能够看倒数第二格和倒数第五格,也是相同的;不相同那便是我抄错了;憋说奥,还真抄错了,上面那个d应该是1101,我写成了1011,前面不改了,从下面这个改,下面括号里便是改之前的:

100 0 0000 0011 1101 0100 0011 1110 1111 0101 0000 1111 1011 1101(这儿从1011改的) 0100 0 1000010

分完是这样的:

0 0000 0 (011 110) (1 0100 0) (011 111) (0 1111 0) (101 000) (0 1111 1) (011 110) (1 0100 0 )

诶,这下对上了,倒数第二组和倒数第5组是相同的: 011110。

然后把每个数都对号入座:12312312,只要两组相同的便是3,其他的都能找到3组,只要这个(011111)是只要2组的。所以他必定3了,可是很奇怪,这个3和之前说的3的ASCII不上号,之前写的3应该是51: 110011,对不上,想必你很猎奇,为什么什么分段都能对上,这个数对不上,诶,我也很猎奇;所以我怀疑是我不是我分组的搞错了,于是又验证了一下,从”00000000″,到”99999999″直接分段解出来的,我这儿列一下,的确3和上面的3能对上,和一般ASCII对不上:

字符 ASCII 八位及以上实践存的序列
“0” 110000 (48) 011101 (29)
“1” 110001 (49) 011110 (34)
“2” 110010 (50) 101000 (40)
“3” 110011 (51) 011111 (31)
“4” 110100 (52) 011100 (28)
“5” 110101 (53) 101011 (43)
“6” 110100 (54) 101100 (44)
“7” 110101 (55) 110000 (48)
“8” 110110 (56) 101010 (42)
“9” 110111 (57) 110001 (49)

榜首列是字符,中心是他们的ASCII编号,最终一列是他们在TaggedPointer中实践存的编号。括号里是十进制;一个都对不上,我感觉这就说明必定是有人有意为之,但我不知道为啥,我大略搜了一下,现在没搜到讲这个的文章。

并且列这个表之结合上面12312312的比如发现它是正序摆放的,由于榜首组是(011 110),这个表里(011110)是字符1,所以是依照12312312正序摆放,这个和8位以下的倒序摆放也是反着来的,就很诡异。

定论:

所以全体上的定论概括一下,当运用NSString的TaggedPointer的时分是,在64位中,依照如下摆放规矩:

1.前三位,符号位,榜首位表明是否是一个TaggedPointer,后两位不知道干啥的。

2.后七位,符号位,前四位表明字符的数量,后三位不知道干啥的。

3.中心剩54位,最多能够分出9个6位,所以最多能表明9个字符。

4.在字符数量小于等于7的时分,倒序摆放字符的ASCII,距离00,最多是7 * 6 + 6 * 2 = 54位表明。

5.在字符数量大于7的时分,正序摆放字符,非ASCII,没有距离,最多是9*6 = 54位表明。这个非ASCII的编号,我盲目猜想应该直接有个映射表,或者有什么特定的规矩能够把它们转成ASCII码。

综上,最多能表明的字符数量便是9个。

疑问:

我有三个疑问:

1.为什么小于等于7的时分是倒序摆放,而大于7的时分是正序;都倒序或者都正序我觉得彻底能表明,这儿规矩不一致的用意是什么?

2.为什么小于等于7的时分有距离,而大于7的时分没有距离;都做成没有距离的我也觉得彻底能表明,这儿规矩不一致的用意是什么?

3.字符数大于7的时分,ASCII和它运用的这个彻底对不上的编码都是一个字符6位,为什么没有直接运用ASCII的编号?

有没有研讨过这儿比较深的老哥了解这块解释一下,或者贴个链接来看看,非常猎奇TaggedPointer为什么要这样指定编码规矩。