网站做贩卖毕业论文合法吗/十大计算机培训学校
目录
- 前言
- 什么是Tagged Pointer?
- 引入Tagged Pointer技术之前
- 引入Tagged Pointer之后
- 总结
- Tagged Pointer原理(Tag+Data分析)
- 关闭数据混淆
- MacOS分析
- NSNumber
- NSString
- iOS分析
- 判断Tagged Pointer
- Tagged Pointer应用
- Tagged Pointer 注意点
- Tagged Pointer注释
- 创建Tagged Pointer
- Tagged Pointer初始化
- 初始变量设置
- Tagged Pointer注册校验
- 生成Tagged Pointer指针
- 相关题目
前言
学习Tagged Pointer原理,参考文章:
老生常谈内存管理(五):Tagged Pointer
什么是Tagged Pointer?
由于NSNumber、NSDate、NSString
一类的变量本身的值需要占用的内存大小常常不需要8个字节,拿整数来说,4 个字节所能表示的有符号整数就可以达到 20 多亿(注:2^31=2147483648,另外 1 位作为符号位),对于绝大多数情况都是可以处理的
为了节省内存和提高执行效率,苹果提出了Tagged Pointer
的概念。对于64位程序,引入Tagged Pointer
后,相关逻辑能减少一半的内存占用,以及3倍的访问速度提升,100倍的创建、销毁速度提升
引入Tagged Pointer技术之前
对象存储在堆上,对象的指针存储的是堆中对象的地址值(即指向堆中的对象)
从内存上看
这样的存储方式会在CPU位数升级的情况下导致占用存储翻倍(以NSNumber对象为例):
32位系统下的指针占4字节,64位系统下的指针占8字节
从效率上看
为了存储和访问一个 NSNumber 对象,我们需要在堆上为其分配内存,另外还要维护它的引用计数,管理它的生命期。这些都给程序增加了额外的逻辑,造成运行效率上的损失
引入Tagged Pointer之后
会将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,表示这是一个特别的指针,不指向任何一个地址。所以,引入了Tagged Pointer对象之后,64位CPU下NSNumber的内存图变成了以下这样:
如图,Tagged Pointer是将值的信息直接存储到了指针本身里面
要注意的是,当8字节可以承载用于表示的数值时,系统就会以Tagged Pointer的方式生成指针,如果8字节承载不了时,则又用以前的方式来生成普通的指针,才会将对象存储在堆上
由于Tagged Pointer不是对象,所以它的isa
应该是无指向的:
NSNumber *number1 = @7;
总结
- Tagged Pointer专门用来存储小的对象,例如NSNumber和NSDate
- Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free
- 在内存读取上有着3倍的效率,创建时比以前快106倍
使用了Tagged Pointer,NSNumber对象的值直接存储在了指针上,不会在堆上申请内存。则使用一个NSNumber对象只需要指针的8个字节内存就够了,大大的节省了内存占用
Tagged Pointer原理(Tag+Data分析)
关闭数据混淆
在现在的版本中,为了保证数据安全,苹果对Tagged Pointer做了数据混淆,开发者通过打印指针无法判断它是不是一个Tagged Pointer,更无法读取Tagged Pointer的存储数据
所以在分析Tagged Pointer之前,我们需要先关闭Tagged Pointer的数据混淆,以方便我们调试程序。通过设置环境变量OBJC_DISABLE_TAG_OBFUSCATION
为YES
-
进入
Edit Scheme
页面:
-
添加环境变量
OBJC_DISABLE_TAG_OBFUSCATION
并将其设置为YES
: -
添加
OBJC_DISABLE_TAGGED_POINTERS
并设置为YES
,会禁用Tagged Pointer
MacOS分析
NSNumber
MacOS
下NSNumber
的Tagged Pointer位视图:
MacOS下采用 LSB(Least Significant Bit,即最低有效位)为Tagged Pointer标识位
比如,打印存储整型1
的NSNumber变量地址,为0x127
十六进制:
-
1
表示整型1 -
2
表示数据类型为int
倒数第二位 对应数据类型 0 char 1 short 2 int 3 long 4 float 5 double -
7
的二进制为0111
,最后一位1
为Tagged Pointer标识位,表示这个指针是Tagged Pointer;前面三位011
是类标识位,对应十进制为3
,表示NSNumber类
objc4源码可查出各个类的标识位:// objc-internal.h {OBJC_TAG_NSAtom = 0, OBJC_TAG_1 = 1, OBJC_TAG_NSString = 2, OBJC_TAG_NSNumber = 3, OBJC_TAG_NSIndexPath = 4, OBJC_TAG_NSManagedObjectID = 5, OBJC_TAG_NSDate = 6,// ...... }
NSString
MacOS
下NSString
的Tagged Pointer位视图:
MacOS下采用 LSB(Least Significant Bit,即最低有效位)为Tagged Pointer标识位
在64 bit
的MacOS下,如果一个NSString对象指针为Tagged Pointer,那么它的后4位(0-3)作为标识位,第4-7位表示字符串长度,剩余的56位就可以用来存储字符串
比如,打印下列字符串的NSString变量地址:
// MRC 环境
#define HTLog(_var) \
{ \NSString *name = @#_var; \NSLog(@"%@: %p, %@, %lu", name, _var, [_var class], [_var retainCount]); \
}int main(int argc, const char * argv[]) {@autoreleasepool {NSString *a = @"a";NSMutableString *b = [a mutableCopy];NSString *c = [a copy];NSString *d = [[a mutableCopy] copy];NSString *e = [NSString stringWithString:a];NSString *f = [NSString stringWithFormat:@"f"];NSString *string1 = [NSString stringWithFormat:@"abcdefg"];NSString *string2 = [NSString stringWithFormat:@"abcdefghi"];NSString *string3 = [NSString stringWithFormat:@"abcdefghij"];HTLog(a);HTLog(b);HTLog(c);HTLog(d);HTLog(e);HTLog(f);HTLog(string1);HTLog(string2);HTLog(string3);}return 0;
}
/*
a: 0x100002038, __NSCFConstantString
b: 0x10071f3c0, __NSCFString
c: 0x100002038, __NSCFConstantString
d: 0x6115, NSTaggedPointerString
e: 0x100002038, __NSCFConstantString
f: 0x6615, NSTaggedPointerString
string1: 0x6766656463626175, NSTaggedPointerString
string2: 0x880e28045a54195, NSTaggedPointerString
string3: 0x10071f6d0, __NSCFString
*/
从打印结果来看,有三种NSString类型:
类型 | 描述 |
---|---|
__NSCFConstantString | 1. 常量字符串,存储在字符串常量区,继承于 __NSCFString。相同内容的 __NSCFConstantString 对象的地址相同,也就是说常量字符串对象是一种单例,可以通过 == 判断字符串内容是否相同。2. 这种对象一般通过字面值@"..." 创建。如果使用 __NSCFConstantString 来初始化一个字符串,那么这个字符串也是相同的 __NSCFConstantString。 |
__NSCFString | 1. 存储在堆区,需要维护其引用计数,继承于 NSMutableString。2. 通过stringWithFormat: 等方法创建的NSString对象(且字符串值过大无法使用Tagged Pointer存储)一般都是这种类型。 |
NSTaggedPointerString | Tagged Pointer,字符串的值直接存储在了指针上。 |
打印结果分析:
NSString对象 | 类型 | 分析 |
---|---|---|
a | __NSCFConstantString | 通过字面量@"..." 创建 |
b | __NSCFString | a 的深拷贝,指向不同的内存地址,被拷贝到堆区 |
c | __NSCFConstantString | a 的浅拷贝,指向同一块内存地址 |
d | NSTaggedPointerString | 单独对 a 进行 copy(如 c),浅拷贝是指向同一块内存地址,所以不会产生Tagged Pointer;单独对 a 进行 mutableCopy(如 b),复制出来是可变对象,内容大小可以扩展;而Tagged Pointer存储的内容大小有限,因此无法满足可变对象的存储要求。 |
e | __NSCFConstantString | 使用 __NSCFConstantString 来初始化的字符串 |
f | NSTaggedPointerString | 通过stringWithFormat: 方法创建,指针足够存储字符串的值。 |
string1 | NSTaggedPointerString | 通过stringWithFormat: 方法创建,指针足够存储字符串的值。 |
string2 | NSTaggedPointerString | 通过stringWithFormat: 方法创建,指针足够存储字符串的值。 |
string3 | __NSCFString | 通过stringWithFormat: 方法创建,指针不足够存储字符串的值。 |
可以看到,为Tagged Pointer的有d
、f
、string1
、string2
指针,它们的指针值分别为
0x6115
、0x6615
、0x6766656463626175
、0x880e28045a54195
:
- 其中
0x61
、0x66
、0x67666564636261
分别对应字符串的ASCII码 - 最后一位
5
的二进制为0101
,最后一位1
是代表这个指针是Tagged Pointer,010
对应十进制为2
,表示NSString类 - 倒数第二位
1
、1
、7
、9
代表字符串长度
对于string2
的指针值0x880e28045a54195
,虽然从指针中看不出来字符串的值,但其也是一个Tagged Pointer
iOS分析
iOS下则采用 MSB(Most Significant Bit,即最高有效位)为Tagged Pointer标识位
iOS
下NSNumber
的Tagged Pointer位视图:
iOS
下NSString
的Tagged Pointer位视图:
与MacOS主要区别就是标识位的位置不同
判断Tagged Pointer
依据就是Tagged Pointer
的标识位
查以下函数源码:
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
可以看到,_objc_isTaggedPointer
函数的实现是将指针值与一个_OBJC_TAG_MASK
掩码进行按位与运算,查看该掩码:
再查宏判断条件的值:
可以看到,arm64
架构下的掩码为1UL<<63
,x86 64
架构且MacOS系统下的掩码为1UL
,其他情况下的掩码为1UL<<63
简言之,iOS
和arm64
下采用最高有效位为Tagged Pointer的标识位,MacOS
的x86 64
下采用最低有效位为Tagged Pointer的标识位
而存储在堆空间的对象由于内存对齐(16的倍数),它的内存地址的最低有效位为0
。由此可以辨别Tagged Pointer和一般对象指针:
Tagged Pointer应用
objc_msgSend
能识别Tagged Pointer,比如NSNumber
的intValue
方法,直接从指针提取数据,不会进行objc_msgSend
的三大流程,节省了调用开销(消息转发、寻找isa
指向等)
内存管理相关的,如retain
方法中调用的rootRetain
:
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{// 如果是 tagged pointer,直接返回 thisif (isTaggedPointer()) return (id)this; bool sideTableLocked = false;bool transcribeToSideTable = false; isa_t oldisa;isa_t newisa;......
isTaggedPointer()
函数实现:
inline bool
objc_object::isTaggedPointer()
{return _objc_isTaggedPointer(this);
}
Tagged Pointer 注意点
我们知道,所有OC对象都有isa
指针,而Tagged Pointer并不是真正的对象,它没有isa
指针,所以如果你直接访问Tagged Pointer的isa成员
的话,将会有如下警告:
Tagged Pointer注释
源码中对Tagged Pointer有这样一段注释:
/***********************************************************************
* Tagged pointer objects.
*
* Tagged pointer objects store the class and the object value in the
* object pointer; the "pointer" does not actually point to anything.
*
* Tagged pointer objects currently use this representation:
* (LSB)
* 1 bit set if tagged, clear if ordinary object pointer
* 3 bits tag index
* 60 bits payload
* (MSB)
* The tag index defines the object's class.
* The payload format is defined by the object's class.
*
* If the tag index is 0b111, the tagged pointer object uses an
* "extended" representation, allowing more classes but with smaller payloads:
* (LSB)
* 1 bit set if tagged, clear if ordinary object pointer
* 3 bits 0b111
* 8 bits extended tag index
* 52 bits payload
* (MSB)
*
* Some architectures reverse the MSB and LSB in these representations.
*
* This representation is subject to change. Representation-agnostic SPI is:
* objc-internal.h for class implementers.
* objc-gdb.h for debuggers.
**********************************************************************/
翻译:
-
Tagged pointer 指针对象将class和对象数据存储在对象指针中,指针实际上不指向任何东西。
-
Tagged pointer 当前使用此表示形式:
(LSB)(macOS)64位分布如下:
- 1 bit 标记是 Tagged Pointer
- 3 bits 标记类型
- 60 bits 负载数据容量,(存储对象数据)
(MSB)(iOS)64位分布如下:
- tag index 表示对象所属的 class
- 负载格式由对象的 class 定义
- 如果 tag index 是 0b111(7), tagged pointer 对象使用 “扩展” 表示形式
- 允许更多类,但 有效载荷 更小
(LSB)(macOS)(带有扩展内容)64位分布如下:
- 1 bit 标记是 Tagged Pointer
- 3 bits 是 0b111
- 8 bits 扩展标记格式
- 52 bits 负载数据容量,(存储对象数据)
-
在这些表示中,某些体系结构反转了MSB和LSB
从注释中我们得知:
- Tagged pointer存储对象数据目前分为
60bits
负载容量和52bits
负载容量。 - 类标识允许使用扩展形式
那么如何判断负载容量?类标识的扩展类型有那些?我们来看下全面的objc_tag_index_t
源码:
{// 60-bit payloadsOBJC_TAG_NSAtom = 0, OBJC_TAG_1 = 1, OBJC_TAG_NSString = 2, OBJC_TAG_NSNumber = 3, OBJC_TAG_NSIndexPath = 4, OBJC_TAG_NSManagedObjectID = 5, OBJC_TAG_NSDate = 6,// 60-bit reserved// 保留位OBJC_TAG_RESERVED_7 = 7, // 52-bit payloadsOBJC_TAG_Photos_1 = 8,OBJC_TAG_Photos_2 = 9,OBJC_TAG_Photos_3 = 10,OBJC_TAG_Photos_4 = 11,OBJC_TAG_XPC_1 = 12,OBJC_TAG_XPC_2 = 13,OBJC_TAG_XPC_3 = 14,OBJC_TAG_XPC_4 = 15,OBJC_TAG_NSColor = 16,OBJC_TAG_UIColor = 17,OBJC_TAG_CGColor = 18,OBJC_TAG_NSIndexSet = 19,OBJC_TAG_NSMethodSignature = 20,OBJC_TAG_UTTypeRecord = 21,OBJC_TAG_Foundation_1 = 22,OBJC_TAG_Foundation_2 = 23,OBJC_TAG_Foundation_3 = 24,OBJC_TAG_Foundation_4 = 25,OBJC_TAG_FirstUnobfuscatedSplitTag = 136, // 128 + 8, first ext tag with high bit setOBJC_TAG_Constant_CFString = 136,// 前60位负载内容OBJC_TAG_First60BitPayload = 0, // 后60位负载内容OBJC_TAG_Last60BitPayload = 6, // 前52位负载内容OBJC_TAG_First52BitPayload = 8, // 后52位负载内容OBJC_TAG_Last52BitPayload = 263,// 保留位OBJC_TAG_RESERVED_264 = 264
};
小结:
-
区分什么位置为负载内容位
MacOS下采用 LSB 即OBJC_TAG_First60BitPayload、OBJC_TAG_First52BitPayload。
iOS下则采用 MSB 即OBJC_TAG_Last60BitPayload、OBJC_TAG_Last52BitPayload。 -
区分负载数据容量
当类标识为0-6时,负载数据容量为60bits。
当类标识为7时(对应二进制为 0b111),负载数据容量为52bits。 -
类标识的扩展类型有哪些?
如果tag index 是 0b111(7), tagged pointer对象使用 “扩展” 表示形式
类标识的扩展类型为上面OBJC_TAG_Photos_1 ~OBJC_TAG_NSIndexSet。 -
类标识与负载数据容量对应关系
当类标识为0-6时,负载数据容量为 60bits。即OBJC_TAG_First60BitPayload 和 OBJC_TAG_Last60BitPayload,负载数据容量 的取值区间也为 0 - 6。
当类标识为7时,负载数据容量为52bits。即OBJC_TAG_First52BitPayload 和 OBJC_TAG_Last52BitPayload,负载数据容量的取值区间为 8 - 263。
只要一个tag
,既可以区分负载数据容量,也可以区分类标识
创建Tagged Pointer
我们知道了Tagged Pointer展现给我们的是Tag + Data
,那么它底层是怎么生成这种类型的伪指针的呢?为什么NSNumber、NSDate、NSString
会生成,而其他的不会呢?
下面来来研究一下,这几个对象是如何生成Tagged Pointer
的
Tagged Pointer初始化
初始变量设置
_read_images()
函数:
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) {// ...if (DisableTaggedPointers) {// 禁用Tagged Pointer,与环境变量OBJC_DISABLE_TAGGED_POINTERS相关disableTaggedPointers();}// 初始化 TaggedPointer 混淆器:用于保护 Tagged Pointer 上的数据initializeTaggedPointerObfuscator();// ...
}
initializeTaggedPointerObfuscator()
函数:
OBJC_EXPORT uintptr_t objc_debug_taggedpointer_obfuscatorOBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0);static void
initializeTaggedPointerObfuscator(void)
{if (!DisableTaggedPointerObfuscation){
// if (!DisableTaggedPointerObfuscation && dyld_program_sdk_at_least(dyld_fall_2018_os_versions)) {// 将随机数据拉入变量,然后移走所有非有效负载位arc4random_buf(&objc_debug_taggedpointer_obfuscator,sizeof(objc_debug_taggedpointer_obfuscator));objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;#if OBJC_SPLIT_TAGGED_POINTERS// 混淆器不适用于任何扩展标记掩码或非混淆位objc_debug_taggedpointer_obfuscator &= ~(_OBJC_TAG_EXT_MASK | _OBJC_TAG_NO_OBFUSCATION_MASK);// 打乱标签排列器的前七个条目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 {// 对于与旧版 SDK 链接的应用程序,将混淆器变量设置为0,// 防止他们正依赖TaggedPointerobjc_debug_taggedpointer_obfuscator = 0;}
}
objc_debug_taggedpointer_obfuscator
是一个unsigned long
的全局变量- 对于一些旧版环境和环境变量
OBJC_DISABLE_TAG_OBFUSCATION
,禁用数据混淆,设置objc_debug_taggedpointer_obfuscator
为0,不混淆 - 获得
objc_debug_taggedpointer_obfuscator
的值:- 将随机数据放入变量中,然后移走所有非有效位
- 和
~_OBJC_TAG_MASK
作一次按位与&
操作
Tagged Pointer注册校验
为什么NSNumber、NSDate、NSString会转成为伪指针呢?其他的为什么不会呢?
加载程序时,从dyld
库的_dyld_start()
函数开始,经历了多般步骤,开始调用_objc_registerTaggedPointerClass()
函数:
void
_objc_registerTaggedPointerClass(objc_tag_index_t tag, Class cls)
{if (objc_debug_taggedpointer_mask == 0) {_objc_fatal("tagged pointers are disabled");}Class *slot = classSlotForTagIndex(tag);if (!slot) {_objc_fatal("tag index %u is invalid", (unsigned int)tag);}Class oldCls = *slot;if (cls && oldCls && cls != oldCls) {_objc_fatal("tag index %u used for two different classes ""(was %p %s, now %p %s)", tag, oldCls, oldCls->nameForLogging(), cls, cls->nameForLogging());}// 如果尚未设置,则将占位类存储在为扩展tag空间保留的基本标记槽中。// 在注册第一个扩展标记时延迟执行此操作,以便// 旧调试器能够更频繁地正确表征伪指针。*slot = cls;if (tag < OBJC_TAG_First60BitPayload || tag > OBJC_TAG_Last60BitPayload) {// OBJC_TAG_RESERVED_7 == 7Class *extSlot = classSlotForBasicTagIndex(OBJC_TAG_RESERVED_7);if (*extSlot == nil) {extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;*extSlot = (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer;}}
}
- 判断是否禁用了Tagged Pointer,若禁用,则终止程序
- 根据指定
tag
获取类指针。若tag
被用于两个不同的类,则终止程序 - 判断负载数据容量如果是52bits进行特殊处理,在
OBJC_TAG_RESERVED_7
处存储占位类OBJC_CLASS_$___NSUnrecognizedTaggedPointer
其实这个方法起的名字是注册,其实应该叫校验。校验在全局数组(以tag
进行位操作为索引,类为value
的全局数组)中,用 tag
取出来的类指针 与 注册的类 是否相符
根据指定tag
获取类指针函数classSlotForTagIndex()
:
// 返回指向tag类数组中类存储的指针,如果标记超出范围,则返回 nil。
static ptrauth_taggedpointer_table_entry Class *
classSlotForTagIndex(objc_tag_index_t tag)
{if (tag >= OBJC_TAG_First60BitPayload && tag <= OBJC_TAG_Last60BitPayload) {return classSlotForBasicTagIndex(tag);}if (tag >= OBJC_TAG_First52BitPayload && tag <= OBJC_TAG_Last52BitPayload) {int index = tag - OBJC_TAG_First52BitPayload;
#if OBJC_SPLIT_TAGGED_POINTERSif (tag >= OBJC_TAG_FirstUnobfuscatedSplitTag)return &objc_tag_ext_classes[index];
#endifuintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator>> _OBJC_TAG_EXT_INDEX_SHIFT)& _OBJC_TAG_EXT_INDEX_MASK);return &objc_tag_ext_classes[index ^ tagObfuscator];}return nil;
}
- 根据负载数据容量是
60bits
还是52bits
,区分为类标识是基础类标识还是扩展类标识。也可以说根据tag
类标识区间 判断 tag
是基础类标识,返回classSlotForBasicTagIndex(tag)
的结果tag
是扩展类标识,对tag
进行位
操作,然后取出存在objc_tag_ext_classes
数组里的结果返回
这里有两个重要的全局数组:
#if SUPPORT_TAGGED_POINTERSextern "C" { extern ptrauth_taggedpointer_table_entry Class objc_debug_taggedpointer_classes[_OBJC_TAG_SLOT_COUNT];extern ptrauth_taggedpointer_table_entry Class objc_debug_taggedpointer_ext_classes[_OBJC_TAG_EXT_SLOT_COUNT];
}
#define objc_tag_classes objc_debug_taggedpointer_classes
#define objc_tag_ext_classes objc_debug_taggedpointer_ext_classes#endif
数组objc_tag_classes
:存储苹果定义的几个基础类
数组objc_tag_ext_classes
:存储苹果预留的扩展类
classSlotForBasicTagIndex()
函数:
// 返回指向tag类数组中类存储的指针
// 假定该tag是一个有效的基本tag
static ptrauth_taggedpointer_table_entry Class *
classSlotForBasicTagIndex(objc_tag_index_t tag)
{
#if OBJC_SPLIT_TAGGED_POINTERSuintptr_t obfuscatedTag = _objc_basicTagToObfuscatedTag(tag);return &objc_tag_classes[obfuscatedTag];
#elseuintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator>> _OBJC_TAG_INDEX_SHIFT)& _OBJC_TAG_INDEX_MASK);uintptr_t obfuscatedTag = tag ^ tagObfuscator;// Array index in objc_tag_classes includes the tagged bit itself
# if SUPPORT_MSB_TAGGED_POINTERSreturn &objc_tag_classes[0x8 | obfuscatedTag];
# elsereturn &objc_tag_classes[(obfuscatedTag << 1) | 1];
# endif
#endif
}
- 对
tag
类标识,进行了一系列的位运算 - 根据判断是
macOS or iOS
,来获取objc_tag_classes
数组里面的类指针
生成Tagged Pointer指针
static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
{return _objc_makeTaggedPointer_withObfuscator(tag, value, objc_debug_taggedpointer_obfuscator);
}__attribute__((no_sanitize("unsigned-shift-base")))
static inline void * _Nonnull
_objc_makeTaggedPointer_withObfuscator(objc_tag_index_t tag, uintptr_t value,uintptr_t obfuscator)
{if (tag <= OBJC_TAG_Last60BitPayload) {uintptr_t result =(_OBJC_TAG_MASK | ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));return _objc_encodeTaggedPointer_withObfuscator(result, obfuscator);} else {uintptr_t result =(_OBJC_TAG_EXT_MASK |((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));return _objc_encodeTaggedPointer_withObfuscator(result, obfuscator);}
}
- 根据负载内容位进行区分:传入的
tag
为类标识,同时也可以用于区分负载数据容量,苹果根据不同的负载数据容量对Tagged Pointer
进行了不同的处理 - 对传入objc_tag_index_t tag和value进行位运算:
- 以NSNumber *a = @(1); 为例:
- tag 为OBJC_TAG_NSNumber(3) 二进制:0b011,16进制为 0x0000000000000003,负载数据容量 为 OBJC_TAG_Last60BitPayload
- value 为 数据数值(1) + 数据类型(int 为 2) 16进制为 0x0000000000000012
- 在 iOS 下 源码中的宏定义:
- _OBJC_TAG_MASK :#define _OBJC_TAG_MASK (1UL<<63)
- _OBJC_TAG_INDEX_SHIFT:#define _OBJC_TAG_INDEX_SHIFT 60
- _OBJC_TAG_PAYLOAD_RSHIFT:#define _OBJC_TAG_PAYLOAD_RSHIFT 4
- 对tag 和 value进行运算得到指针result:uintptr_t result = (_OBJC_TAG_MASK | ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
- (uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT):tag 为 0x0000000000000003 左移 _OBJC_TAG_INDEX_SHIFT(60) 得到十六进制: 0x3000000000000000
- ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT):value 为0x0000000000000012,位运算后为 0x0000000000000012
- result 为 _OBJC_TAG_MASK(1UL<<63) 和 0x3000000000000000 和 0x0000000000000012 进行 “或” 操作
- result 为 0xb000000000000012
- 以NSNumber *a = @(1); 为例:
- 进行编码(数据混淆,数据保护):对result (0xb000000000000012)进行编码,我们看下:
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{return _objc_encodeTaggedPointer_withObfuscator(ptr, objc_debug_taggedpointer_obfuscator);
}static inline void * _Nonnull
_objc_encodeTaggedPointer_withObfuscator(uintptr_t ptr, uintptr_t obfuscator)
{uintptr_t value = (obfuscator ^ ptr);
#if OBJC_SPLIT_TAGGED_POINTERSif ((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;
#endifreturn (void *)value;
}static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{return _objc_decodeTaggedPointer_withObfuscator(ptr, objc_debug_taggedpointer_obfuscator);
}static inline uintptr_t
_objc_decodeTaggedPointer_withObfuscator(const void * _Nullable ptr,uintptr_t obfuscator)
{uintptr_t value= _objc_decodeTaggedPointer_noPermute_withObfuscator(ptr, obfuscator);
#if OBJC_SPLIT_TAGGED_POINTERSuintptr_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;
#endifreturn value;
}
无论是编码还是解码,都是对tagged pointers
与objc_debug_taggedpointer_obfuscator
来进行 “异或” 操作
相关题目
执行下面两段代码:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; ++i) {dispatch_async(queue, ^{person.name = [NSString stringWithFormat: @"abcdefghij"];});
}
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; ++i) {dispatch_async(queue, ^{person.name = [NSString stringWithFormat: @"abcdefghi"];});
}
这两段代码的区别就是字符串长度少了一位,恰这个临界长度导致了字符串类型的不同:
第一段代码中
name
是NSCFString
类型
看第一段代码崩溃的地方,objc_release
函数执行报错:
__NSCFString
存储在堆上,它是个正常对象,需要维护引用计数的。name
通过setter
方法为其赋值。而setter方法的实现如下:
- (void)setName:(NSString *)name {if(_name != name) {[_name release];_name = [name copy]; // or [name retain]}
}
异步并发执行setter
方法,可能就会有多条线程同时执行[_name release]
,连续release
两次就会造成对象的过度释放,导致Crash
解决方案:
-
使用
atomic
原子性关键字@property (atomic, copy)NSString* name;
-
加锁
dispatch_queue_t queue = dispatch_get_global_queue(0, 0); for (int i = 0; i < 1000; ++i) {dispatch_async(queue, ^{// 加锁person.name = [NSString stringWithFormat: @"abcdefghij"];// 解锁}); }
第二段代码中
name
中是NSTaggedPointerString
类型
在objc_release
函数中会判断指针是不是TaggedPointer类型,是的话就不对对象进行release
操作,也就避免了因过度释放对象而导致的Crash
,因为根本就没执行释放操作
__attribute__((aligned(16), flatten, noinline))
void
objc_release(id obj)
{if (!obj) return;if (obj->isTaggedPointer()) return; // 直接返回,不会释放return obj->release();
}
相关文章:

【iOS】Tagged Pointer
目录 前言什么是Tagged Pointer?引入Tagged Pointer技术之前引入Tagged Pointer之后总结 Tagged Pointer原理(TagData分析)关闭数据混淆MacOS分析NSNumberNSString iOS分析 判断Tagged PointerTagged Pointer应用Tagged Pointer 注意点 Tagge…...

Mysql explain 优化解析
explain 解释 select_type 效率对比 MySQL 中 EXPLAIN 语句的 select_type 列描述了查询的类型,不同的 select_type 类型在效率上会有所差异。下面我们来比较一下各种 select_type 的效率: SIMPLE: 这是最简单的查询类型,表示查询不包含子查询或 UNION 操作。 这种查询通常是…...

wget下载github文件得到html文件
从github/gitee下载源文件,本来是22M下载下来只有11k 原因: Github会提供html页面,包括指定的文件、上下文与相关操作。通过wget或者curl下载时,会下载该页面 解决方式: github点击Code一栏的raw按钮,获得源…...

【es】elasticsearch 自定义排序-按关键字位置排序
一 背景 要求es查询的结果按关键字位置排序,位置越靠前优先级越高。 es版本7.14.0,项目是thrift,也可以平替springboot,使用easyes连接es。 二 easyes使用 配easyes按官方文档就差不多了 排序 | Easy-Es 主要的一个问题是easy…...

堆的相关知识点
目录 大小堆 堆的实现 堆的创建 堆的销毁 交换 向上调整 向下调整 弹出首个元素 取出首个元素 判空 堆插入 大小堆 大堆:最上面的数字是最小的,越往下越大 小堆:最上面的数字是最大的,越往下越小 堆的复杂程度&#…...

【Sass】常用全局sass高级函数,可使用原子化CSS减轻代码量,方便快速开发
文章目录 前言一、安装二、样式custom.scssflex.scsscolor.scssmargin-padding.scssorther 总结 前言 提示:这里可以添加本文要记录的大概内容: 针对style的预编译器为scss 转载自git前端知识库 原博主是B站up程序员郑清,可以看他的v3教程…...

MYSQL 第四次作业
任务要求: 具体操作: 新建数据库: mysql> CREATE DATABASE mydb15_indexstu; Query OK, 1 row affected (0.01 sec) mysql> USE mydb15_indexstu; Database changed 新建表: mysql> CREATE TABLE student( ->…...

depcheck 前端依赖检查
介绍 depcheck 是一款用于检测项目中 未使用依赖项 的工具。 depcheck 通过扫描项目文件,帮助你找出未被引用的依赖,从而优化项目。 优势: 简单易用: 仅需几个简单的命令,就能够扫描并列出未使用的依赖项,让你快速了…...

Qt/C++音视频开发79-采集websocket视频流/打开ws开头的地址/音视频同步/保存到MP4文件/视频回放
一、前言 随着音视频的爆发式的增长,各种推拉流应用场景应运而生,基本上都要求各个端都能查看实时视频流,比如PC端、手机端、网页端,在网页端用websocket来接收并解码实时视频流显示,是一个非常常规的场景,单纯的http-flv模式受限于最大6个通道同时显示,一般会选择ws-f…...

网络安全等级保护制度1.0与2.0的演进与变革
等保1.0概述 等保1.0是我国在网络安全领域迈出的重要一步,它于2008年正式发布。该版本的等保制度以《信息安全技术 信息系统安全等级保护基本要求》为核心标准,主要聚焦于信息系统的物理安全、网络安全、主机安全、应用安全和数据安全等方面的基础防护。…...

多线程优化API请求:CountDownLatch与PriorityBlockingQueue的应用
目录 前言 CountDownLatch是什么? PriorityBlockingQueue是什么? 场景描述 解决方案 定义统一工厂制造类 定义制造厂 定义客户请求实现 定义控制器 定义启动类 结果呈现 启动项目 请求制造操作 总结 前言 写这篇文章的缘由是因为之前在面…...

谷粒商城实战笔记-54-商品服务-API-三级分类-拖拽效果
文章目录 一,54-商品服务-API-三级分类-修改-拖拽效果1,el-tree控件加上允许拖拽的属性2,是否允许拖拽3,完整代码 一,54-商品服务-API-三级分类-修改-拖拽效果 本节的主要内容是给三级分类树形结构加上拖拽功能&#…...

AI大模型学习必备十大网站
随着人工智能技术的快速发展,AI大模型(如GPT-3、BERT等)在自然语言处理、计算机视觉等领域取得了显著的成果。对于希望深入学习AI大模型的开发者和研究者来说,找到合适的学习资源至关重要。本文将为大家推荐十大必备网站ÿ…...

Elasticsearch:Golang ECS 日志记录 - zap
ECS 记录器是你最喜欢的日志库的格式化程序/编码器插件。它们可让你轻松地将日志格式化为与 ECS 兼容的 JSON。 编码器以 JSON 格式记录日志,并在可能的情况下依赖默认的 zapcore/json_encoder。它还处理 ECS 错误格式的错误字段记录。 默认情况下,会添…...

关于线性代数(考研)
1.AE的特征值的问题 若λ是A的特征值,对应的特征向量是x,则Axλx,所以(AE)xAxExλxx(λ1)x,所以λ1是AE的特征值。所以若A的特征值是1,1,0,则AE的特征值就是11,11,01&am…...

【java基础】spring springMVC springboot 的区别
Spring, Spring MVC, 和 Spring Boot 是三个紧密相关的技术,它们都是由 Pivotal 团队(原SpringSource)开发的,主要用于构建企业级的Java应用程序。尽管它们在功能上有所交集,但各自也有独特的定位和用途。 Spring Fra…...

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 开源项目热度排行榜(100分) - 三语言AC题解(Python/Java/Cpp)
🍭 大家好这里是清隆Coding ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,支持题目在线评测,专栏文章质量平均 93 分 最新华为OD机试目录…...

大模型算法面试题(十一)
本系列收纳各种大模型面试题及答案。 1、说一下目前主流或前沿的预训练模型,包括nlp(百度ERNIE3.0,华为NEZHA,openAI gpt-3,nvidia MegatronLM,macrosoft T5)和cv(我只知道CLIP&…...

CSS 基础知识
CSS(级联样式表)是设置 Web 内容样式的代码。CSS 基础知识将介绍入门所需的内容。我们将回答以下问题:如何将文本设置为红色?如何使内容显示在(网页)布局中的某个位置?如何用背景图片和颜色装饰我的网页? 什么是CSS? 像HTML一样,CSS不是一种编程语言。它也不是一种标…...

IntelliJ IDEA 和 Eclipse的区别
IntelliJ IDEA 和 Eclipse 是两个非常流行的 Java 集成开发环境(IDE),它们各自具有不同的特点和优势。下面是它们之间的一些主要对比: 性能和资源使用 IntelliJ IDEA 被认为在某些方面更加智能,能够提供更好的代码分…...

Ansible之playbook剧本编写(二)
tags 模块 可以在一个playbook中为某个或某些任务定义“标签”,在执行此playbook时通过ansible-playbook命令使用--tags选项能实现仅运行指定的tasks。 playbook还提供了一个特殊的tags为always。作用就是当使用always作为tags的task时,无论执行哪一个t…...

力扣第二十九题——两数相除
内容介绍 给你两个整数,被除数 dividend 和除数 divisor。将两数相除,要求 不使用 乘法、除法和取余运算。 整数除法应该向零截断,也就是截去(truncate)其小数部分。例如,8.345 将被截断为 8 ,-…...

解析三款热门的文献翻译工具:优势与使用指南
今儿咱们来聊聊那些让咱们头疼又不得不面对的事儿——文献翻译。在浩瀚的学术海洋里遨游时,遇到外文文献那是家常便饭,但语言障碍就像海上的迷雾,一不小心就能让你偏离航向。别担心,我这不就带着几款亲测好用的文献翻译神器来了嘛…...

git 过滤LFS文件下载
git config --global filter.lfs.smudge "git-lfs smudge --skip -- %f" git config --global filter.lfs.process "git-lfs filter-process --skip" 恢复下载 git config --global filter.lfs.smudge "git-lfs smudge -- %f" git config --g…...

内存泄漏详解
文章目录 什么是内存泄漏内存泄漏的原因排查及解决内存泄漏避免内存泄漏及时释放资源设置合理的变量作用域及时清理不需要的对象避免无限增长避免内部类持有外部类引用使用弱引用 什么是内存泄漏 内存泄漏是指不使用的对象持续占有内存使得内存得不到释放,从而造成…...

多角度解析高防CDN防御DDOS及CC攻击
网络攻击的形式也日益多样化,其中DDoS(分布式拒绝服务)和CC(Challenge Collapsar)攻击尤为突出,给网站和企业带来了巨大的安全威胁。高防CDN(Content Delivery Network)作为一种专业…...

(7) cmake 编译C++程序(二)
文章目录 概要整体代码结构整体代码小结 概要 在ubuntu下,通过cmake编译一个稍微复杂的管理程序 整体代码结构 整体代码 boss.cpp #include "boss.h"Boss::Boss(int id, string name, int dId) {this->Id id;this->Name name;this->DeptId …...

C语言系统调用linux文件系统
在C语言中,open、write和read函数是系统调用(system calls),它们直接由操作系统提供,用于底层的文件操作。这些函数是UNIX和类UNIX系统(如Linux)中的标准接口,不同于C标准库中的文件…...

LeetCode142 环形链表 II
前言 题目: 142. 环形链表 II 文档: 代码随想录——环形链表 II 编程语言: C 解题状态: 思路错误,链表不允许被修改 思路 两步走,第一步,判断有没有环,第二步,判断入环口…...

逆向案例二十八——某高考志愿网异步请求头参数加密,以及webpack
网址:aHR0cDovL3d3dy54aW5nYW9rYW90Yi5jb20vY29sbGVnZXMvc2VhcmNo 抓包分析,发现请求头有参数u-sign是加密的,载荷没有进行加密,直接跟栈分析。 进入第二个栈,打上断点,分析有没有加密位置。 可以看到参数…...