- 上述代码能正常运行,打印内容如下:
Demo[13193:1133563] My name is <ViewController: 0x7fe536c07830>
-
提示:使用
实例对象在内存中的布局
、super 调用本质
、栈空间分配问题
、消息机制
、访问成员变量的本质
这些知识点来解决这个问题
1. 问题解析,我们对上述题目,主要存在两点疑惑
- 为什么
print
能够正常调用? - 为什么
self.name
变成了ViewController
?
2. 为什么 print
能够正常调用?
-
id cls = [YYPerson class]
是拿到了 YYPerson 类对象的地址,放到指针变量cls
中 -
void *obj = &cls
是定义一个指针变量obj
,指向&cls
-
[(__bridge id)obj print]
是将obj
强转成id
类型,然后调用print
方法
- 我们将
obj指针
看成person指针
,他们都指向一个地址 -
person指针
指向的内存,存放着isa指针
和_name字符串指针
,所以在内存中占用 16字节。 -
obj指针
指向cls指针
和未知内容的8字节
,在内存中占用16字节 -
cls
和isa
都指向YYPerson类对象
- 所以从实例和类对象直接的关系来看:
obj
等价于person
- 因此
[(__bridge id)obj print]
能够正常打印
3. 为什么 self.name
变成了 ViewController
?
- 从题目2的内存结构图中可以得知,我们
假实例占用16字节
,本来应该是_name指针
,现在被未知的8字节内容占据了 - 所以问题可以转换成
cls所在内存未知
后面连续的 8 字节内容是什么呢?
4. 我们还需要了解 [super viewDidLoad];
super
的本质是什么?
- 我们之前学过
super
的本质,所以上述代码的本质如下:
objc_msgSendSuper({self, [UIViewController class]},@selector()viewDidLoad)
-
{self, [UIViewController class]}
是一个临时
结构体
5. 在此之前我们还补充栈
相关知识
-
静态变量
: 会被放在内存的静态区 -
常量
: 会被放在内存的常量区 -
程序员alloc出来的相关类实例
:会被存放在堆区(这个堆
和数据结构里面的堆
是两码事) -
函数的参数值、局部变量的值
: 会被存放在栈区(这个栈的操作
和数据结构的栈
很相似)
6. 所以,我们可以绘制出 -viewDidLoad
函数内部的栈空间占用情况
函数内部的栈空间占用情况
- 由此我们可以得出紧挨着
cls
的self
会被我们的程序当成_name指针
,所以,我们的self.name 变成了ViewController
7. 修改代码验证我们的说法
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString *abc = @"hahahaha";
id cls = [YYPerson class];
void *obj = &cls;
[(__bridge id)obj print];
}
- 这时候在栈空间中
self
和cls
中间会插入abc
,按照我们的分析,abc
将会被我们的程序误认为是self.name
打印出来. - 打印结果如下:
Demo[13379:1187071] My name is hahahaha
8. 我们通过Xcode打印栈空间的地址,取得地址上的内容,来侧面印证我们的想法
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [YYPerson class];
void *obj = &cls;
NSLog(@"断点处");
[(__bridge id)obj print];
}
- 打印如下
(lldb) p/x &obj
(void **) $1 = 0x00007ffeedb16a70
(lldb) x/4g 0x00007ffeedb16a70
0x7ffeedb16a70: 0x00007ffeedb16a78 0x00000001020e8ea0
0x7ffeedb16a80: 0x00007fa4d3004bc0 0x00000001020e8e28
(lldb) po 0x00007ffeedb16a78
<YYPerson: 0x7ffeedb16a78>
(lldb) po 0x00000001020e8ea0
YYPerson
(lldb) po 0x00007fa4d3004bc0
<ViewController: 0x7fa4d3004bc0>
(lldb) po 0x00000001020e8e28
ViewController
-
四个地址,分别对应
obj
cls
self
[ViewController class]
-
打印出来的内容与我们的猜想一致,更好的从侧面印证我们的说法。
-
x/4g 0x00007ffeedb16a70
是从0x00007ffeedb16a70
地址开始,每组的内容占据内存的8个字节,打印4组内容
网友评论