likes
comments
collection
share

关于TaggedPoiner中字符排列的一些探究(疑惑)

作者站长头像
站长
· 阅读数 6

最近准备面试基础知识被问麻了;于是研究了一些比较热门的知识点,其中一个就是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为什么要这样指定编码规则。

转载自:https://juejin.cn/post/7348105395163758603
评论
请登录