Objective-C中的對象的內(nèi)存布局

Objective-C的本質(zhì)

  • Objc的底層實現(xiàn)是C\C++代碼:objc->C\C++->匯編->機器語言
  • Objc的對象伟众,類主要是基于C\C++中的結(jié)構(gòu)體實現(xiàn)
  • 將Objc代碼轉(zhuǎn)換為C\C++代碼
    xcrun  -sdk  iphoneos  clang  -arch  arm64  -rewrite-objc OC源文件  -o  輸出的CPP文件
    

一個OC對象在內(nèi)存中如何布局的痒筒?

NSObject對象的底層實現(xiàn)

  • OC代碼
    @interface NSObject <NSObject> {
     Class isa;
    }
    
  • 對應(yīng)的C++代碼
    struct NSObject_IMPL {
        Class isa; // 8個字節(jié)
    };
    
  • 通過runtime和malloc_size函數(shù)獲取對應(yīng)的內(nèi)存大小,發(fā)現(xiàn) class_getInstanceSize獲取到NSObject的實例成員變量所占用的內(nèi)存為8字節(jié)掂摔,malloc_size獲取到objc指針指向內(nèi)存大小為16個字節(jié)瘫想,意思就是說,編譯器分配16個字節(jié)殖氏,實際用到8個字節(jié)
    NSObject *obj = [[NSObject alloc] init];
    // 16個字節(jié)
    
    // 獲得NSObject實例對象的成員變量所占用的大小 >> 8
    NSLog(@"%zd", class_getInstanceSize([NSObject class]));
    
    // 獲得obj指針?biāo)赶騼?nèi)存的大小 >> 16
    // 編譯器分配了16個字節(jié)附井,實際用到8個字節(jié)
    NSLog(@"%zd", malloc_size((__bridge const void *)obj));
  • 通過objc源碼也能證明這一點译秦,[[NSObject alloc] init];最終會調(diào)用 allocWithZone:(struct _NSZone *)zone,落腳到 size_t instanceSize(size_t extraBytes),這個方法會保證實例的最小內(nèi)存為16個字節(jié)劣纲,objc源碼的調(diào)用流程如下:
     + (id)allocWithZone:(struct _NSZone *)zone {
         return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
     }
 
     id
     _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
     {
         id obj;

     #if __OBJC2__
         // allocWithZone under __OBJC2__ ignores the zone parameter
         (void)zone;
         obj = class_createInstance(cls, 0);
     #else
         if (!zone) {
             obj = class_createInstance(cls, 0);
         }
         else {
             obj = class_createInstanceFromZone(cls, 0, zone);
         }
     #endif

         if (slowpath(!obj)) obj = callBadAllocHandler(cls);
         return obj;
     }
    
     id class_createInstance(Class cls, size_t extraBytes)
     {
         return _class_createInstanceFromZone(cls, extraBytes, nil);
     }
 
     id
     _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                                   bool cxxConstruct = true,
                                   size_t *outAllocatedSize = nil)
     {
         if (!cls) return nil;

         assert(cls->isRealized());

         // Read class's info bits all at once for performance
         bool hasCxxCtor = cls->hasCxxCtor();
         bool hasCxxDtor = cls->hasCxxDtor();
         bool fast = cls->canAllocNonpointer();
        // 獲取到實例的大小
         size_t size = cls->instanceSize(extraBytes);
         if (outAllocatedSize) *outAllocatedSize = size;

         id obj;
         if (!zone  &&  fast) {
             obj = (id)calloc(1, size);
             if (!obj) return nil;
             obj->initInstanceIsa(cls, hasCxxDtor);
         }
         else {
             if (zone) {
                 obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
             } else {
                 obj = (id)calloc(1, size);
             }
             if (!obj) return nil;

             // Use raw pointer isa on the assumption that they might be
             // doing something weird with the zone or RR.
             obj->initIsa(cls);
         }

         if (cxxConstruct && hasCxxCtor) {
             obj = _objc_constructOrFree(obj, cls);
         }

         return obj;
     }
 
     size_t instanceSize(size_t extraBytes) {
         size_t size = alignedInstanceSize() + extraBytes;
         // CF requires all objects be at least 16 bytes.
        // 保證最小大小為16個字節(jié)
         if (size < 16) size = 16;
         return size;
     }

繼承NSObject的類的對象內(nèi)存布局

  • OC代碼
     @interface Student : NSObject
     {
         @public
         int _no;
         int _age;
     }
    @end
  • 對應(yīng)的C++代碼
    struct Student_IMPL {
        struct NSObject_IMPL NSObject_IVARS;
        int _no;
        int _age;
    };

    struct NSObject_IMPL {
        Class isa;
    };
  • 等價于
    struct Student_IMPL {
        Class isa;
        int _no;
        int _age;
    };
  • Student的內(nèi)存大小分別由isa逢捺,_no, _age組成癞季, isa指針占用8個字節(jié)劫瞳,兩個int類型的數(shù)據(jù),共占用8個字節(jié)绷柒,所以一個Student對象占用16個字節(jié)志于,編譯分配了16個字節(jié),并且Student對象剛好用完
    Student *stu = [[Student alloc] init];
    stu->_no = 4;
    stu->_age = 5;
    // 16
    NSLog(@"%zd", class_getInstanceSize([Student class]));
    // 16
    NSLog(@"%zd", malloc_size((__bridge const void *)stu));
  • 從Xcode中ViewMemory也可以看出
image.png
  • 可視化的內(nèi)存布局如下


    image.png
  • 同理废睦,具體繼承關(guān)系內(nèi)存布局如下

    • OC代碼
@interface Person : NSObject
{
    @public
    int _age;
}
@end

@interface Student : Person
{
    @public
    int _no;
}
@end

@interface Graduate : Student
{
    @public
    int grade;
}
@end

@implementation Graduate

@end
  • 對應(yīng)的C++代碼
struct NSObject_IMPL {
    Class isa;
};

struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS; // 8
    int _age; // 4
};

struct Student_IMPL {
    struct Person_IMPL Person_IVARS; 
    int _no; 
}; 


 Student *stu = [[Student alloc] init];
 stu->_age = 2;
 stu->_no = 3;
 NSLog(@"stu - %zd", class_getInstanceSize([Student class]));  // 16
 NSLog(@"stu - %zd", malloc_size((__bridge const void *)stu)); // 16

 Graduate *grade = [[Graduate alloc] init];
grade->_age = 7;
grade->_no = 8;
grade->grade = 9;
NSLog(@"grade - %zd", class_getInstanceSize([Graduate class])); // 24
NSLog(@"grade - %zd", malloc_size((__bridge const void *)grade)); // 32
  • 64位系統(tǒng)下伺绽, 綜上可以看出,編譯給Person分配了16個字節(jié)嗜湃,實際用到12個字節(jié)奈应,還有4個字節(jié)沒有使用, Student對象繼承Person對象,Student對象實際會繼續(xù)使用Person對象內(nèi)存中沒有使用的4個字節(jié)购披,所以Student對象的內(nèi)存大小是16個字節(jié)杖挣,Grade繼承Student類,實際占用的內(nèi)存大小為20個字節(jié)刚陡,由于內(nèi)存對齊原因惩妇,以8個字節(jié)內(nèi)存對齊株汉,所以會使用24個字節(jié),編譯器分配了32個字節(jié)
  • 內(nèi)存對齊:結(jié)構(gòu)體的大小必須是最大成員大小的倍數(shù)
Student內(nèi)存數(shù)據(jù).png
Student內(nèi)存布局.png
Grade內(nèi)存數(shù)據(jù).png

總結(jié)

  • 說白了就是多了一個isa指針屿附,isa的大小由編譯器郎逃,系統(tǒng)決定,內(nèi)存分配編譯器按照內(nèi)存對齊進行大小分配挺份,分配的內(nèi)存褒翰,不一定會完全用完
  • class_getInstanceSize獲取實例對象至少占用的內(nèi)存
  • malloc_size獲取編譯器給實例對象對象分配的內(nèi)存的大小
  • libmalloc源碼中定義了 #define NANO_MAX_SIZE 256 /* Buckets sized {16, 32, 48, 64, 80, 96, 112, ...}表示在iOS系統(tǒng)中每次最少分配16個字節(jié),或者每次分配的字節(jié)數(shù)是16的倍數(shù)匀泊,每次最多分配256字節(jié)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末优训,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子各聘,更是在濱河造成了極大的恐慌揣非,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件躲因,死亡現(xiàn)場離奇詭異早敬,居然都是意外死亡,警方通過查閱死者的電腦和手機大脉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門搞监,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人镰矿,你說我怎么就攤上這事琐驴。” “怎么了秤标?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵绝淡,是天一觀的道長。 經(jīng)常有香客問我苍姜,道長牢酵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任衙猪,我火速辦了婚禮茁帽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘屈嗤。我一直安慰自己潘拨,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布饶号。 她就那樣靜靜地躺著铁追,像睡著了一般。 火紅的嫁衣襯著肌膚如雪茫船。 梳的紋絲不亂的頭發(fā)上琅束,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天扭屁,我揣著相機與錄音,去河邊找鬼涩禀。 笑死料滥,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的艾船。 我是一名探鬼主播葵腹,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼屿岂!你這毒婦竟也來了践宴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤爷怀,失蹤者是張志新(化名)和其女友劉穎阻肩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體运授,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡烤惊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吁朦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柒室。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖喇完,靈堂內(nèi)的尸體忽然破棺而出伦泥,到底是詐尸還是另有隱情剥啤,我是刑警寧澤锦溪,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站府怯,受9級特大地震影響刻诊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜牺丙,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一则涯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冲簿,春花似錦粟判、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吝沫,卻和暖如春呻澜,著一層夾襖步出監(jiān)牢的瞬間递礼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工羹幸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留脊髓,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓栅受,卻偏偏與公主長得像将硝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子窘疮,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容