1、内存布局
在前面,我们也提到过应用程序中的内存地址分配,主要分为代码段、数据段、堆、栈、内核区,如下图所示:
//数据段: 全局变量(已初始化)
int a = 10;
//数据段: 全局变量(未初始化)
int b;
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//数据段: 静态变量(已初始化)
static int c = 20;
//数据段: 静态变量(未初始化)
static int d;
//栈:局部变量(未初始化)
int e;
//栈: 局部变量(已初始化)
int f = 20;
//数据段: 字符串常量
NSString *str = @"123";
//堆
NSObject *obj = [[NSObject alloc] init];
}
@end
2、Tagged Pointer
NSNumber *number1 = @(10);
NSLog(@"number1---> %p",number1);
NSNumber *number2 = @(100000000);
NSLog(@"number2---> %p",number2);
NSNumber *number3 = @(100000000000000000);//18位
NSLog(@"number3---> %p",number3);
打印结果:
===========================================
number1---> 0xf244e62484888f3b
number2---> 0xf244e624dbd69f9b
number3---> 0x6000026d1b40
看上面的例子,你会不会感到疑惑,他们都是NSNumber类型的,但是为什么他们的地址格式不一样呢?
答:这是因为从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储。
只有当指针不够存储数据时,才会使用原先动态分配内存的方式来存储数据。
objc_msgSend能识别Tagged Pointer,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销。


3、扩展
- <1>下面这两段代码执行的效果怎么样?
//代码段1:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i<1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefjhijk"];
});
}
//代码段2:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i<1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abc"];
});
}
答:代码段1会发生崩溃,代码段2不会崩溃。
因为代码段1崩溃的原因是因为多线程的安全隐患。多条线程同时读写同一资源变量时,导致每个线程都会在name的setName方法里进行release等操作,name会被释放多次,造成坏内存访问。
那为什么代码段2不会崩溃呢?因为较短的字符串“abc”是用Tagged Pointer来存储的。它是直接将地址值赋给name的,简洁来说,它并不是一个OC对象,没有OC对象的set、get方法,也没有release,所以不会崩溃。
-
<2>如何判断一个指针是否为Tagged Pointer?
根据底层源码可以得知:
iOS平台,最高有效位是1(第64bit)
Mac平台,最低有效位是1
这两种情况下,指针为Tagged Pointer。
网友评论