OC 對象內(nèi)存探索(內(nèi)存對齊)

OC底層原理學(xué)習(xí)

iOS中獲取內(nèi)存大小的三種方式

  1. sizeof
    得到的結(jié)果是數(shù)據(jù)類型占用的空間大小,傳入的參數(shù)是數(shù)據(jù)類型,在編譯階段就會確定的大小
  2. class_getInstanceSize
    導(dǎo)入頭文件runtime.h,runtime提供的api,獲取對象的內(nèi)存大小
  3. malloc_size
    導(dǎo)入頭文件malloc.h,獲取系統(tǒng)實(shí)際分配的內(nèi)存大小

NSObject內(nèi)存對齊

結(jié)果
sizeof:sizeof 打印的是 objc的指針,一個(gè)指針8字節(jié)
class_getInstanceSizeNSObject沒有任何屬性,但是他有一個(gè)isa指針,所以它的內(nèi)存大小也是8字節(jié)
malloc_size:16?why?帶著疑問去看alloc源碼
上一章提到alloc 源碼核心方法是 _class_createInstanceFromZone,這個(gè)方法做了3件事

  1. cls->instanceSize(extraBytes): 計(jì)算需要開辟多大的內(nèi)存空間
  2. (id)calloc(1, size): 申請內(nèi)存,返回地址指針
  3. obj->initInstanceIsa(cls, hasCxxDtor) :將類與isa指針關(guān)聯(lián)

這一章我們分析一下內(nèi)存

instanceSize源碼

    size_t instanceSize(size_t extraBytes) const {
        //編譯器快速計(jì)算內(nèi)存大小
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            //加斷點(diǎn)走到這里
            return cache.fastInstanceSize(extraBytes);
        }
        //計(jì)算類中所有屬性的大小 + extraBytes(這時(shí)候是0)
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        //如果size 小于 16,取16 ,16對齊
        if (size < 16) size = 16;
        return size;
    }

fastInstanceSize源碼

  size_t fastInstanceSize(size_t extra) const
    {
        ASSERT(hasFastInstanceSize(extra));

        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        } else {
            //加斷點(diǎn)走到這里
            size_t size = _flags & FAST_CACHE_ALLOC_MASK;
            // remove the FAST_CACHE_ALLOC_DELTA16 that was added
            // by setFastInstanceSize
            return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
        }
    }

align16 16字節(jié)對齊算法

static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}

總結(jié)
NSObject alloc后,開辟的內(nèi)存大小在計(jì)算完所有屬性大小后,還要再來個(gè)16字節(jié)對齊!malloc_size獲取的就是對象實(shí)際占用的內(nèi)存大小

那么新的問題來了, 為什么要16字節(jié)對齊?原因有以下幾點(diǎn)

  • 通常內(nèi)存是由一個(gè)個(gè)字節(jié)組成的,cpu在存取數(shù)據(jù)時(shí),并不是以字節(jié)為單位存儲,而是以塊為單位存取,塊的大小為內(nèi)存存取力度。頻繁存取字節(jié)未對齊的數(shù)據(jù),會極大降低cpu的性能,所以可以通過減少存取次數(shù)來降低cpu的開銷
  • 16字節(jié)對齊,是由于在一個(gè)對象中,第一個(gè)屬性isa占8字節(jié),當(dāng)然一個(gè)對象肯定還有其他屬性,當(dāng)無屬性時(shí),會預(yù)留8字節(jié),即16字節(jié)對齊,如果不預(yù)留,相當(dāng)于這個(gè)對象的isa和其他對象的isa緊挨著,容易造成訪問混亂(蘋果早期是8字節(jié)對齊,現(xiàn)在新的系統(tǒng)是16字節(jié)對齊)
  • 16字節(jié)對齊后,可以加快CPU讀取速度,同時(shí)使訪問更安全,不會產(chǎn)生訪問混亂的情況

結(jié)構(gòu)體內(nèi)存對齊

//1、定義兩個(gè)結(jié)構(gòu)體
struct Mystruct1{
    char a;     //1字節(jié)
    double b;   //8字節(jié)
    int c;      //4字節(jié)
    short d;    //2字節(jié)
}Mystruct1;

struct Mystruct2{
    double b;   //8字節(jié)
    int c;      //4字節(jié)
    short d;    //2字節(jié)
    char a;     //1字節(jié)
}Mystruct2;

兩個(gè)結(jié)構(gòu)體除了變量順序不一樣以外都一樣,結(jié)果確不一樣,這就是因?yàn)閮?nèi)存對齊

8字節(jié)對齊規(guī)則

每個(gè)特定平臺上的編譯器都有自己的默認(rèn)“對齊系數(shù)”(也叫對齊模數(shù))。程序員可以通過預(yù)編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數(shù),其中的n就是你要指定的“對齊系數(shù)”。在ios中,Xcode默認(rèn)為#pragma pack(8),即8字節(jié)對齊

結(jié)構(gòu)體內(nèi)存對齊原則

  1. 數(shù)據(jù)成員的對齊規(guī)則可以理解為min(m, n) 的公式, 其中 m表示當(dāng)前成員的開始位置, n表示當(dāng)前成員所需要的位數(shù)。如果滿足條件 m 整除 n (即 m % n == 0), n 從 m 位置開始存儲, 反之繼續(xù)檢查m+1 能否整除 n, 直到可以整除, 從而就確定了當(dāng)前成員的開始位置。
  2. 數(shù)據(jù)成員為結(jié)構(gòu)體:當(dāng)結(jié)構(gòu)體嵌套了結(jié)構(gòu)體時(shí),作為數(shù)據(jù)成員的結(jié)構(gòu)體的自身長度作為外部結(jié)構(gòu)體的最大成員的內(nèi)存大小,比如結(jié)構(gòu)體a嵌套結(jié)構(gòu)體b,b中有char、int、double等,則b的自身長度為8
  3. 最后結(jié)構(gòu)體的內(nèi)存大小必須是結(jié)構(gòu)體中最大成員內(nèi)存大小的整數(shù)倍,不足的需要補(bǔ)齊。

結(jié)構(gòu)體內(nèi)存對齊驗(yàn)證


Mystruct1的內(nèi)存分布

Mystruct2的內(nèi)存分布

驗(yàn)證一下原則2,定義一個(gè)Mystruct3

struct Mystruct3{
    char a;     //1字節(jié)
    struct Mystruct1 struct1;
}Mystruct3;

總結(jié)
在定義結(jié)構(gòu)體的時(shí)候,根據(jù)內(nèi)存從大到小的順序定義,可以省內(nèi)存空間
那么NSObject 會不會有這個(gè)問題呢?繼續(xù)

NSObject內(nèi)存優(yōu)化

定義一個(gè)繼承于NSObjectPerson

@interface Person : NSObject

@property (nonatomic, copy) NSString * name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int height;

@end


person對象的內(nèi)存分布并不是像結(jié)構(gòu)體那樣,第一個(gè)是isa指針,第二個(gè)不是name,第三個(gè)是name,把第二個(gè)分開打印,發(fā)現(xiàn)第二個(gè)里面實(shí)際存儲了ageage,順序都變化了,實(shí)際是蘋果幫我們做了內(nèi)存優(yōu)化,屬性重排

person的內(nèi)存分布

接下來我算下person的大小,isa指針8字節(jié),age、height 8字節(jié),name8字節(jié),再根據(jù)16字節(jié)算法,開辟的內(nèi)存大小32?打印結(jié)果如下

總結(jié)
計(jì)算屬性內(nèi)存的大小時(shí)class_getInstanceSize按照8字節(jié)對齊計(jì)算,但是真正開辟的內(nèi)存空間大小malloc_size是按照16字節(jié)對齊計(jì)算

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容