iOS 底层探索:内存平移的分析验证

iOS 底层探索: 学习大纲 OC篇

前言

  • 这篇文章主要通过一个例子,来分析内存平移,并加深对类的研究。

举例分析

@interface LGPerson : NSObject
- (void)sayHello;
@end
@implementation LGPerson
- (void) saySomething { NSLog(@"%s: 你好", __func__); }
@end

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad 
{
    [super viewDidLoad];
   // 指针读取类地址,强转为对象,调用sayHello。
    Class cls = [LGPerson class];
    void * ht = &cls;
    [(__bridge id)ht saySomething];
    
    // 实例化对象,调用sayHello
    LGPerson * person = [LGPerson new];
    [person saySomething];
}
@end

发现打印如下: 相同的结果
-[LGPerson saySomething] : 你好
-[LGPerson saySomething] : 你好

我们在写saySomething的时候会发现
image.png

这里有代码的自动提示

  • 那我们就要提出疑问了,为什么会这样子呢?我们先猜想 ,指针读取类地址,强转为对象这个肯定就是person对象了;

分析如下:

  • 我们知道对象方法的本质是:对象调用对象方法,是对象的isa指针去指向类本身,让类本身调用该对象方法。
  • 其次方法本质其实就是objc_msgSend进行消息转发,其中经过一系列的方法查找的过程;在快速查找过程中,就是去cache中查找方法,在这里回顾一下类和对象的数据结构如下:


所以经过上面的分析,我们就可以知道,为什么通过获取类的首地址可以调用该对象方法,得出一个结论:就是类的首地址通过是可以通过强转为这个person的对象的。

那么我们给类加个属性然后再方法中打印试试如下:

- (void)saySomething
{ 
    NSLog(@"%s - %@",__func__,self.kc_hobby); //kc_hobby是个字符串
}

{ 
    Class cls = [LGPerson class];
    void  *kc = &cls;  //
    [(__bridge id)kc saySomething];
    
    LGPerson *person = [LGPerson alloc];
    [person saySomething]; // self.kc_name = nil - (null)
 }  
---------
 -[LGPerson saySomething] - ViewController
 -[LGPerson saySomething] - (null)

第二个打印为null,我们都知道,但是为什么第一个self.kc_hobby打印的为ViewController呢?
首先我们先打印下这个kc如下:

解释*(void **)的作用:

  • (void**) 代表的是指向指针的指针。 读取是地址值

  • 加上 * 进行解引用:*(void **) ,可读取到地址中存放的内容。

分析:在给属性赋值的时候发现kc是没法调用属性的set方法,其次在进行内存平移读取属性的值的时候,很明显,发生的异常,在内存中属性会遵循内存对齐原则,字符串占8个字符。其实这里读到viewcontroller是发生了越界读取。这里也可以看出来,(__bridge id)kc 只是一个只有指针的对象,并不包含任何属性信息。
总结: &cls 只是骗编译器 他是个isa 其实他不是 他只是和isa 一样 指向了LGProson。

拓展: 结构体压栈

可以通过自定义一个结构体,判断结构体内部成员的压栈情况

所以图中可以得出 20先加入,再加入10,因此结构体内部的压栈情况是 低地址->高地址,递增的,栈中结构体内部的成员是反向压入栈,即低地址->高地址,是递增的,

回到kc的案例,可以通过下面这段代码打印下栈的存储:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // ViewController 当前的类
    // self cmd (id)class_getSuperclass(objc_getClass("LGTeacher")) self cls kc person
    Class cls = [LGPerson class];
    void  *kc = &cls;  // 
    LGPerson *person = [LGPerson alloc];
    person.kc_hobby = @"KC";
    NSLog(@"%p - %p",&person,kc);
    // 隐藏参数 会压入栈帧
    void *sp  = (void *)&self;
    void *end = (void *)&person;
    long count = (sp - end) / 0x8;
    
    for (long i = 0; i<count; i++) {
        void *address = sp - 0x8 * I;
        if ( i == 1) {
            NSLog(@"%p : %s",address, *(char **)address);
        }else{
            NSLog(@"%p : %@",address, *(void **)address);
        }
    }
    
    // LGPerson  - 0x7ffeea0c50f8
    [(__bridge id)kc saySomething]; // 1 2  - <ViewController: 0x7f7f7ec09490>
    
    [person saySomething]; // self.kc_name = nil - (null)
    //
}

// 打印如下:

<ViewController: 0x100f076a0> [对应的 self ]
viewDidLoad  [对应的 _cmd]
ViewController [对应的 superClass]
<ViewController: 0x100f076a0> [对应的self]
LGPerson  [对应的cls]
<LGPerson: 0x16f44db48>  [对应的kc]
  • 打印顺序为:
    self ->_cmd -> superclass -> self -> cls -> ht

补充:
函数内部定义的局部变量和数组,都存放在栈区; (比如每个函数都有的(id self, SEL _cmd))

Clang当前的ViewController.m文件。

clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m
image.png

所以内存地址从高位到低位的顺序为:
self -> _cmd ->__rw_objc_super对象->cls->ht

看下__rw_objc_super结构体:

struct __rw_objc_super { 
    struct objc_object *object;  //在当前控制器里 self
    struct objc_object *superClass; //在当前控制器里 ViewController
    __rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {} 
};

结构体内部元素,是后面元素先插入内存栈中。所以__rw_objc_super内部的内存顺序为: ViewController -> self

最终内存顺序为: self -> _cmd -> ViewController -> self -> cls -> ht

其中为什么class_getSuperclassViewController,因为objc_msgSendSuper2返回的是当前类,两个self,并不是同一个self,而是栈的指针不同,但是指向同一片内存空间

  • 普通person流程:person -> kc_hobby - 内存平移8字节

  • kc流程:经过内存平移+8,即为self,指向<ViewController:>

其中 person 与 LGPerson的关系是 person是以LGPerson为模板的实例化对象,即alloc有一个指针地址,指向isa,isa指向LGPerson,它们之间关联是有一个isa指向;

而kc也是指向LGPerson的关系,编译器会认为 kc也是LGPerson的一个实例化对象,即kc相当于isa,即首地址,指向LGPerson,具有和person一样的效果,简单来说,我们已经完全将编译器骗过了,即kc也有`kc_hobby。由于person查找kc_hobby是通过内存平移8字节,所以kc也是通过内存平移8字节去查找kc_hobby。

哪些东西在栈里 哪些在堆里?

  • alloc的对象 都在

  • 指针、对象中,例如person指向的空间中,person所在的空间在栈中

  • 临时变量

  • 属性值,属性随对象是在

注意:

  • 是从小到大,即低地址->高地址
  • 是从大到小,即从高地址->低地址分配
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容