由 Tagged Pointer 联想到的一个问题

前言

最近和基友 Maize 聊天,他给我普及了一个有意思的知识点,回看唐巧的 深入理解Tagged Pointer 的文章,再结合之前在公司看到的代码,突然有了一些灵感,我们先上一段代码。

@interface NSObject (AssociatedObject) 
@property (nonatomic, assign) CGFloat someProperty; 
@end 
@implementation NSObject (AssociatedObject) 
@dynamic someProperty; 
- (void)setSomeProperty:(CGFloat)someProperty{  return objc_setAssociatedObject(self, @selector(someProperty), @(someProperty), OBJC_ASSOCIATION_ASSIGN); 
} 
- (CGFloat)someProperty{  return [objc_getAssociatedObject(self, @selector(someProperty)) floatValue]; 
} 
@end 

如果此时我们给 someProperty 属性赋值并打印该属性的值,你认为它会 crash 么? 我们先说一下结论,如果这个属性的值为100,那么它不会 crash,如果是 100.1 那么就一定会 crash。 如果你马上就明白了其中的原理,那么恭喜你,你完全不用再花时间看此篇文章了。 如果你没有意识到其中的问题,不妨花上 10 分钟时间来仔细读读这篇文章的内容。

Tagged Pointer 是什么?

本节内容是节选自唐巧的文章 - 深入理解Tagged Pointer

在 2013 年的 WWDC 上,Apple 推出了首个 64 位架构的双核处理器,为了节省内存和提高执行效率,Tagged Pointer 概念诞生了。Apple 宣称引入该技术后,相关逻辑能减少一半的内存占用,以及 3 倍的访问速度提升,100 倍的创建、销毁速度提升。

为了能让大家更好的理解上面代码的问题,我们需要了解 Tagged Pointer 的一些实现细节。

我们知道 NSNumber、NSDate 一类的变量本身的值需要占用的内存大小常常不需要 8 个字节,拿整数来说,4 个字节所能表示的有符号整数就可以达到 20 多亿(注:2^31=2147483648,另外 1 位作为符号位),对于绝大多数情况都是可以处理的。

所以 Apple 将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,表示这是一个特别的指针,不指向任何一个地址。所以,引入了 Tagged Pointer 对象之后,64 位 CPU 下 NSNumber 的内存图变成了以下这样:

int main(int argc, char * argv[])
{@autoreleasepool {NSNumber *number1 = @1;NSNumber *number2 = @2;NSNumber *number3 = @3;NSNumber *numberFFFF = @(0xFFFF);NSLog(@"number1 pointer is %p", number1);NSLog(@"number2 pointer is %p", number2);NSLog(@"number3 pointer is %p", number3);NSLog(@"numberffff pointer is %p", numberFFFF);return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));}
} 

运行之后,我们得到的结果如下,可以看到,除去最后的数字最末尾的 2 以及最开头的 0xb,其它数字刚好表示了相应 NSNumber 的值。

number1 pointer is 0xb000000000000012
number2 pointer is 0xb000000000000022
number3 pointer is 0xb000000000000032
numberFFFF pointer is 0xb0000000000ffff2 

可见,苹果确实是将值直接存储到了指针本身里面。我们还可以猜测,数字最末尾的 2 以及最开头的 0xb 是否就是苹果对于 Tagged Pointer 的特殊标记呢?我们尝试放一个 8 字节的长的整数到 NSNumber 实例中,对于这样的实例,由于 Tagged Pointer 无法将其按上面的压缩方式来保存,那么应该就会以普通对象的方式来保存,我们的实验代码如下:

NSNumber *bigNumber = @(0xEFFFFFFFFFFFFFFF);
NSLog(@"bigNumber pointer is %p", bigNumber); 

运行之后,结果如下,验证了我们的猜测,bigNumber 的地址更像是一个普通的指针地址,和它本身的值看不出任何关系:

bigNumber pointer is 0x10921ecc0 

Tagged Pointer 带来的问题?

我们结合一下刚才的代码,试想如果我们有以下两种使用场景,哪一种会有问题呢?

  • 代码片段 1

// CodeSnippets 1
self.view.someProperty = 100;
NSLog(@"%@", @(self.view.someProperty)); 
  • 代码片段 2

//CodeSnippets 2
self.view.someProperty = 100.1;
NSLog(@"%@", @(self.view.someProperty)); 

不管你是通过代码实验的,还是已经想明白了,第二个代码片段是会造成 crash 的,而且 Xcode 会提示这是一个野指针方面的问题。

那么结合刚才所说的 Tagged Pointer,我们怎么解释这个问题呢?

当赋值为 100 时,由于 Tagged Pointer 的优化,@(100) 生成的 NSNumber 对象并不是一个严格意义上的对象,所以系统不会在堆上开辟内存存储对象,值是直接保存在地址指针里面,在取出的时候也就不会出现任何释放的问题。

而当赋值为 100.1 时,由于 Tagged Pointer 对这种值没法进行优化,@(100.1) 生成的 NSNumber 对象是一个真正意义上的对象,所以此时存储下来的是一个地址指针,但由于在关联的时候我们选用了 OBJC_ASSOCIATION_ASSIGN,那么此时系统并不会帮我们去进行那些计数引用等操作,所以当我们想再取出的时候,就会出问题了,也就是野指针的问题。

至此,应该很好的解释了前言中那段代码的问题。

结论

那么我们应该如何优化这段代码呢?

  • 假设你真的想存一个 CGFloat,由于在存取过程中操作的是一个对象,不妨将 objc_AssociationPolicy 选项中的 OBJC_ASSOCIATION_ASSIGN 换成 OBJC_ASSOCIATION_RETAIN_NONATOMIC

  • 如果可以的话,建议直接保存一个 NSNumber 类型的对象来替换 CGFloat 类型的数值,毕竟这样会更安全,记得同样把关联策略设定为 OBJC_ASSOCIATION_RETAIN_NONATOMIC

关于 CGFloat 还需要注意的一点是:在不同的 CPU 下,它的真实类型是不一样的,有时是 float,有时是 double,那么在从 NSNumber 做转换的时候,到底要返回何种类型,仍然需要开发者做好判断。

  • 在 Apple 的 API 文档中,OBJC_ASSOCIATION_ASSIGN 说是用于 weak 类型的引用,但并不是 ARC 范围内的 weak,严格来说应该更类似于 unsafe_unretained 的概念,如果真的要用 OBJC_ASSOCIATION_ASSIGN 策略,请时候考虑好对象的生命周期,避免不必要的 crash 。

参考 NSHipster 的文章Associated Objects

  • 如果你是声明一个 ARC 范围内的 weak 属性,你可能需要一个类似弱引用表概念的东西,不妨阅读下瓜神的这篇文章 weak 弱引用的实现方式

网络安全入门学习路线

其实入门网络安全要学的东西不算多,也就是网络基础+操作系统+中间件+数据库,四个流程下来就差不多了。

1.网络安全法和了解电脑基础

其中包括操作系统Windows基础和Linux基础,标记语言HTML基础和代码JS基础,以及网络基础、数据库基础和虚拟机使用等...

别被这些看上去很多的东西给吓到了,其实都是很简单的基础知识,同学们看完基本上都能掌握。计算机专业的同学都应该接触了解过,这部分可以直接略过。没学过的同学也不要慌,可以去B站搜索相关视频,你搜关键词网络安全工程师会出现很多相关的视频教程,我粗略的看了一下,排名第一的视频就讲的很详细。 当然你也可以看下面这个视频教程仅展示部分截图 学到http和https抓包后能读懂它在说什么就行。

2.网络基础和编程语言

3.入手Web安全

web是对外开放的,自然成了的重点关照对象,有事没事就来入侵一波,你说不管能行吗! 想学好Web安全,咱首先得先弄清web是怎么搭建的,知道它的构造才能精准打击。所以web前端和web后端的知识多少要了解点,然后再学点python,起码得看懂部分代码吧。

最后网站开发知识多少也要了解点,不过别紧张,只是学习基础知识。

等你用几周的时间学完这些,基本上算是具备了入门合格渗透工程师的资格,记得上述的重点要重点关注哦! 再就是,要正式进入web安全领域,得学会web渗透,OWASP TOP 10等常见Web漏洞原理与利用方式需要掌握,像SQL注入/XSS跨站脚本攻击/Webshell木马编写/命令执行等。

这个过程并不枯燥,一边打怪刷级一边成长岂不美哉,每个攻击手段都能让你玩得不亦乐乎,而且总有更猥琐的方法等着你去实践。

学完web渗透还不算完,还得掌握相关系统层面漏洞,像ms17-010永恒之蓝等各种微软ms漏洞,所以要学习后渗透。可能到这里大家已经不知所云了,不过不要紧,等你学会了web渗透再来看会发现很简单。

其实学会了这几步,你就正式从新手小白晋升为入门学员了,真的不算难,你上你也行。

4.安全体系

不过我们这个水平也就算个渗透测试工程师,也就只能做个基础的安全服务,而这个领域还有很多业务,像攻防演练、等保测评、风险评估等,我们的能力根本不够看。

所以想要成为一名合格的网络工程师,想要拿到安全公司的offer,还得再掌握更多的网络安全知识,能力再更上一层楼才行。即便以后进入企业,也需要学习很多新知识,不充实自己的技能就会被淘汰。

从时代发展的角度看,网络安全的知识是学不完的,而且以后要学的会更多,同学们要摆正心态,既然选择入门网络安全,就不能仅仅只是入门程度而已,能力越强机会才越多。

尾言

因为入门学习阶段知识点比较杂,所以我讲得比较笼统,最后联合CSDN整理了一套【282G】网络安全从入门到精通资料包,需要的小伙伴可以点击链接领取哦! 网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值