objc里的伪指针TaggedPointer

如果你看过我前面两篇objc函数枢纽msgSend你印象中的NSString是这样吗,相信已经多次看过它的身影了,到底它是何物何作用,我今日就来揭开谜团。
我之所为称呼它为伪指针,是因为它像幽灵一样,没有肉身(实例)但却在能像objc对象实例那样表现出各种行为。你能猜出下面代码运行的结果吗?

// non-arc
id unknown = (id)0x12345678;
NSLog(@"%@", unknown);
unknown = (id)0xa000000000032312;
NSLog(@"%@", unknown);
NSString* s12 = [NSString stringWithUTF8String:"12"];
NSLog(@"%@", s12);
[s12 isEqualToString:unknown];
NSLog(@"%d", *(int64_t*)&s12 == *(int64_t*)&unknown);

如果我随便编写一个地址0x12345678,然后用这个地址进行objc调用,[(id)0x12345678 retain]。相信大家第一反应是我正在对一个不明白的地址访问,指向这个地址的指针大家叫它野指针,我正在向一个“野指针”操作。

如果我又随便编写一个地址像这们0xa000000000032312,并对这个地址进行objc调用[(id)0xa000000000032312 retain]。相信大家的反应是和上面举例的情况一样,尽管当中有人看过我们前面的文章知道这是一个TaggedPointer,但是我还是在对一个不可信的地址进行操作,当访问0xa000000000032312内存地址的内容时将要出错。

但结果却是,0xa000000000032312地址的操作合法,并且用NSLog(@"%@", (id)0xa000000000032312)还打印出12。

大家都熟悉实例的构建过程,如[[NSString alloc] init]和[NSString string]。都必须为对像实例分配空间然后初始化。然而地址0xa000000000032312只是我随便编造的,完全没有在那个地址上分配过空间。
我再来调用[NSString stringWithUTF8String:"12"],返回了一个类型是NSTaggedPointerString实例的指针。指向的地址正是0xa000000000032312。直到这时0xa000000000032312的实例才被构建,然而对这个地址一访问还是一个非法地址。

所以TaggedPointer只是一个伪指针,它的真相就在这个指针指向的地址的数值本身。
0xa000000000032312这个数值就像一个被压缩的对像实例一样,最高4位是isa的线索,参考前面介绍反汇编msgSend文章,最低8位是info信息,中间第9位起的48位是对像的content。用这副幽灵镜来再看0xa000000000032312,什么都一清二楚了,{isa=0xa,coutent=‘\x31\x32‘,len=2}。这是NSString中一种名为EightBitsEncoding的情况。

为什么要有TaggedPointer,就让我们来看一下性能。
用profile分别查看循环100M次的[@"ab" stringAppendByString:@"c"]和[NSString stringWithUTF8String:"abc"];
上一篇的例子可以找到[@"ab" stringAppendByString:@"c"]返回__NSCFString, [NSString stringWithUTF8String:"abc"]返回NSTaggedPointerString。
究竟它们之间有什么样的性能差别呢?由于我的机器性能有限,profile时几近运行不过来,所以没有图可截贴出,只能陈述一下情况。先是[@"ab" stringAppendByString:@"c"]返回__NSCFString的情况,CFString不断地在缓慢分配,待到分配了1M个时,内存占用了50M,机器在profile运行中惨不忍睹,我果断中止了。另一方面[NSString stringWithUTF8String:"abc"]返回NSTaggedPointerString的profile中,CFString数量没有增长,内存自然也没有消耗。好我将循环次数减少至1M次,不做profile,直接运行对比,NSTaggedPointerString的情况明显要快出几倍,因为根本没有构建过实例(,用于返回的实例,中间里面的过程一样还有其它临时实例的)。

除了NSTaggedPointerString还有其它的TaggedPointer,你知道0xb0000000000000c2是什么吗,赶紧试一试。

最后多谢大家再次观看。

时间: 2024-08-08 21:25:05

objc里的伪指针TaggedPointer的相关文章

【转】JavaScript里的this指针

情形一:传入的参数是函数的别名,那么函数的this就是指向window: 情形二:传入的参数是被new过的构造函数,那么this就是指向实例化的对象本身: 情形三:如果我们想把被传入的函数对象里this的指针指向外部字面量定义的对象,那么我们就是用apply和call 我们可以通过代码看出我的结论,代码如下: <script type="text/javascript"> var name = "I am window"; var obj = { nam

Objc中2维指针作为输出参数时由ARC及@autoreleasepool引发的血案

先看下面一个例子 #import <UIKit/UIKit.h> #import "AppDelegate.h" @interface Something : NSObject - (void)doWithError:(NSError **)outError; @end @implementation Something - (void)doWithError:(NSError **)outError { @autoreleasepool { *outError = [NS

(转)C++11里的智能指针

1. std::auto_ptr有些违背c++编程思想. 已经被"不建议使用了".2. 下文转自:http://blog.csdn.net/lanergaming/article/details/24273419 c#和java中有自动垃圾回收机制,.net运行时和java虚拟机可以管理分配的堆内存,在对象失去引用时自动回收,因此在c#和jva中, 内存管理不是大问题.c++语言没有垃圾回收机制,必须自己去释放分配的堆内存,否则就会内存泄露. 我相信大部分c++开发人员都遇到过内存泄露

Keil C51里关于堆栈指针的处理

Keil C是非常优秀的C51编译器,可能是最好的C51编译器,提供各种优化模式,对变量的优化和地址安排做得非常好.这是用C语言写代码的好处之一,如果用汇编写,得费一大番功夫给各个变量安排内存物理地址,还得时刻记住哪些地址的内存单元是已经分配了,新增加的变量就不能占用那些已经分配了的单元,以免产生内存交叠冲突和溢出.我一直非常信赖Keil C51的编译结果,在我的印象里,它对内存的分配是完美的,只要代码用它编译时没有报告任何warning和error,代码运行时不可能内存冲突或溢出的现象.但,今

如何检测字符串在Obj-C里是否为空?

本文选自StackOverflow(简称:SOF)精选问答汇总系列文章之一,本系列文章将为读者分享国外最优质的精彩问与答,供读者学习和了解国外最新技术.本文将为读者讲解如何检测字符串在Objective-C里是否为空. 问题: Jamey McElveen 如何检测NSString在Objective-C里是否为空? 答案: Marc Charbonneau 可以检查是否[string length] == 0.这将检查它是否是一个有效但是为空的字符串(@"")以及其是否为无值.因为调

反汇编看c++引用

继续反汇编系列,本次使用vc2008在x86体系下分析c++中的引用. 定义一个引用类型和将一个变量转换成引用类型一样吗? 引用比指针安全,真的是这样吗,对引用不理解的话比指针还危险. 为什么要用常量引用传参,只是为了只读? 先来说明一下下面使用到的词汇: 对象:不是OO里的对象,而是泛指在c++语言中某种类型(内嵌,结构体,类)的实例,与变量相同的意思. 存储体: "the standard (draft 3225, section [basic.life]) which clearly st

C#委托与C语言函数指针及函数指针数组

C#委托与C语言函数指针及函数指针数组 在使用C#时总会为委托而感到疑惑,但现在总新温习了一遍C语言后,才真正理解的委托. 其实委托就类似于C/C++里的函数指针,在函数传参时传递的是函数指针,在调用的时候通过指针访问这个函数. 在C语言中函数指针的申明如下: //可以理解为申明一个指着变量 Func ,它的类型是 返回Type(可以为 void )类型的参数,接收 (Type one,Type two,...)类型的//参数(可以不接受参数). Type *Func(Type one,Type

C++成员函数指针错误用法警示(成员函数指针与高性能的C++委托,三篇),附好多评论

今天做一个成绩管理系统的并发引擎,用Qt做的,仿照QtConcurrent搞了个模板基类.这里为了隐藏细节,隔离变化,把并发的东西全部包含在模板基类中.子类只需注册需要并发执行的入口函数即可在单独线程中执行.最终目标是,继承的业务逻辑类外部调用时有两个接口可选,调用syncRun同步执行:调用由引擎自动生成的asyncRun就异步执行.最终自动生成asyncRun的模板基类没能实现,主要原因是mingw对this处理的太有问题了!!原本以为编译器问题,后来才知道成员函数指针和this指针如此特殊

深入浅出剖析C语言函数指针与回调函数(二)

上一篇博文的地址: http://blog.csdn.net/morixinguan/article/details/65494239 这节,我们来看看函数指针与回调函数在Linux内核中的应用. 从上节我们了解到,函数指针和回调函数在开发者和用户之间的一个例子,那么这节,我将引用Linux内核中文件操作结构体来详细的说明. 我们首先来看到这个结构体,这段代码位于linux内核的include/linux/fs.h中,由于代码众多,我只截取几个最基本的例子: File_operations文件操