日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS经典面试题之深入分析“内存平移”的原理

發布時間:2024/5/21 编程问答 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS经典面试题之深入分析“内存平移”的原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、拋磚引玉

  • 現在有一個 YDWPerson 類 ,其中有一個屬性 name 和一個實例方法 saySomething,如下:
@interface YDWPerson : NSObject@property (nonatomic, copy) NSString *name;- (void)saySomething;@end@implementation YDWPerson- (void)saySomething {NSLog(@"%s",__func__);}@end
  • 通過以下代碼的方式,能否調用實例方法?為什么?
Class cls = [YDWPerson class];void *boy = &cls;[(__bridge id)boy saySomething];

二、調試分析

① 不訪問類變量
  • 在日常開發中,我們采取的是如下的調用方式:
YDWPerson *person = [YDWPerson alloc];[person saySomething];
  • 不過,運行上面的代碼,可以看到兩種方式都可以調用成功,如下所示:

  • [person saySomething] 的本質是對象發送消息,那么當前的 person 是什么呢?我們來一步一步分析。
  • 首先,person 的 isa 指向類 YDWPerson,即 person 的首地址指向 YDWPerson 的首地址,我們可以通過 YDWPerson 的內存平移找到 cache,在 cache 中查找方法:

  • [(__bridge id)boy saySomething] 中的 boy 是來自于 YDWPerson 這個類,有一個指針 boy,將其指向 YDWPerson 的首地址:

  • 因此,person 是指向 YDWPerson 類的結構,boy 也是指向 YDWPerson 類的結構,然后都是在 YDWPerson 中的 methodList 中查找方法:

② 訪問打印類變量
  • 現在,我們繼續修改 saySomething 方法實現,如下所示:
@implementation YDWPerson- (void)saySomething {NSLog(@"%s %@",__func__, self.name);}@end
  • 再次運行,查看打印結果:

  • 可以看到:boy 調用打印的 name 是 <ViewController: 0x7fbee94051c0>,而 person 調用打印的 name 則是 (null),為什么打印不一致呢?
③ 原理分析
  • 首先 person 調用 name,是由于 self 指向 person 的內存結構,然后通過內存平移8字節,去取值 name,即 self 指針首地址平移 8 字節獲得,如下:

  • boy 是內存中一個 8 字節的指針,指向 cls,相當于 person 指針指向 YDWPerson 的一個實例,所以 self.name 的獲取,相當于 boy 首地址的指針也需要平移 8 字節尋找 name,那么此時的 boy 的指針地址是多少?平移 8 字節獲得的是什么呢?
  • boy 是一個指針,是存在棧中的,棧是一個先進后出的結構,參數傳入就是一個不斷壓棧的過程:
    • 其中隱藏參數會壓入棧,且每個函數都會有兩個隱藏參數(id self,sel _cmd),可以通過 clang 查看底層編譯;
    • 隱藏參數壓棧的過程,其地址是遞減的,而棧是從高地址->低地址分配的,即在棧中,參數會從前往后一直壓棧(棧是一個先進后出的隊列,內存從高地址到低地址分配,所以先壓入棧的地址高);
    • super 通過 clang 查看底層的編譯,是 objc_msgSendSuper,其第一個參數是一個結構體 __rw_objc_super(self,class_getSuperclass);
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));Class cls = ((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("YDWPerson"), sel_registerName("class"));void *boy = &cls;YDWPerson *person = ((YDWPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("YDWPerson"), sel_registerName("alloc"));((void (*)(id, SEL))(void *)objc_msgSend)((id)(__bridgeid)boy, sel_registerName("saySomething"));((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("saySomething"));}
  • 因此入棧的變量如下:self–>_cmd–> cls–> boy–> person,但是[super viewDidLoad];調用時會產生一個結構體傳入參數,因此這個結構體也會被壓入到當前棧中:self–>_cmd–>(id)class_getSuperclass(objc_getClass(“YDWTeacher”))–>self–> cls–> boy–> person;
    • self 和 _cmd 是 viewDidLoad 方法的兩個隱藏參數,是高地址->低地址正向壓棧的;
    • class_getSuperClass 和 self 為 objc_msgSendSuper2 中的結構體成員,是從最后一個成員變量,即低地址->高地址反向壓棧;
  • 添加以下代碼:
Class cls = [YDWPerson class];void *boy = &cls;YDWPerson *person = [YDWPerson alloc];NSLog(@"%p - %p", &person, boy);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);}}[(__bridge id)boy saySomething];[person saySomething];
  • 運行結果如下所示:
// 0x7ffee77cd058:person 0x7ffee77cd068:boy2021-03-20 20:19:16.190498+0800 內存平移[68463:5044422] 0x7ffee77cd058 - 0x7ffee77cd068// self2021-03-20 20:19:16.190649+0800 內存平移[68463:5044422] 0x7ffee77cd088 : <ViewController: 0x7f7fbe2045f0>// _cmd2021-03-20 20:19:16.190745+0800 內存平移[68463:5044422] 0x7ffee77cd080 : viewDidLoad// superclass2021-03-20 20:19:16.190853+0800 內存平移[68463:5044422] 0x7ffee77cd078 : ViewController// self2021-03-20 20:19:16.190934+0800 內存平移[68463:5044422] 0x7ffee77cd070 : <ViewController: 0x7f7fbe2045f0>// cls2021-03-20 20:19:16.191027+0800 內存平移[68463:5044422] 0x7ffee77cd068 : YDWPerson// boy 2021-03-20 20:19:16.191135+0800 內存平移[68463:5044422] 0x7ffee77cd060 : <YDWPerson: 0x7ffee77cd068>2021-03-20 20:19:16.191238+0800 內存平移[68463:5044422] -[YDWPerson saySomething] <ViewController: 0x7f7fbe2045f0>2021-03-20 20:19:16.191333+0800 內存平移[68463:5044422] -[YDWPerson saySomething] (null)
  • 其中,為什么 class_getSuperclass 是 ViewController,因為 objc_msgSendSuper2返回的是當前類,兩個self,并不是同一個self,而是棧的指針不同,但是指向同一片內存空間;
    • [(__bridge id)boy saySomething]調用時,此時的 boy 是 YDWPerson: 0x7ffee77cd068,所以 saySomething 方法中傳入 self 的還是 YDWPerson,但并不是我們通常認為的 YDWPerson,而是我們當前傳入的消息接收者,即YDWPerson: 0x7ffee77cd068,是 YDWPerson 的實例對象,此時的操作與普通的 YDWPerson 是一致的,即 YDWPerson 的地址內存平移 8 字節;
    • 普通person流程:person -> name - 內存平移8字節;
    • boy 流程:0x7ffee77cd068 + 0x80 -> 0x7ffee77cd070,即為self,指向<ViewController: 0x7f7fbe2045f0>,如下圖所示:

  • 其中 person 與 YDWPerson 的關系是 person 是以 YDWPerson 為模板的實例化對象,即 alloc 有一個指針地址,指向 isa,isa 指向 YDWPerson,它們之間關聯是有一個 isa 指向。
  • 而 boy 也是指向 YDWPerson 的關系,編譯器會認為 boy 也是 YDWPerson 的一個實例化對象,即 boy 相當于 isa,即首地址,指向 YDWPerson,具有和 person 一樣的效果,簡單來說,我們已經完全將編譯器“騙”過了,即 boy 也有 name。由于person 查找 name 是通過內存平移 8 字節,所以 boy 也是通過內存平移 8 字節去查找 name;
④ 棧和堆分別存放了什么?
  • alloc 的對象存放在堆中;
  • 指針、對象存放于棧中,例如 person 指向的空間在堆中,person 所在的空間在棧中;
  • 臨時變量存放棧中;
  • 屬性值存放在堆中,屬性隨對象是存放在棧中;

三、總結

  • 堆是從小到大,即低地址 -> 高地址;
  • 棧是從大到小,即從高地址 -> 低地址分配;
    • 函數隱藏參數會從前往后一直壓棧,即從高地址 -> 低地址 開始入棧,
    • 結構體內部的成員是從低地址 -> 高地址;
  • 一般情況下,內存地址有如下規則:
    • 0x60 開頭表示在堆中;
    • 0x70 開頭的地址表示在棧中;
    • 0x10 開頭的地址表示在全局區域中。

總結

以上是生活随笔為你收集整理的iOS经典面试题之深入分析“内存平移”的原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。