通過(guò)內(nèi)存對(duì)齊分析IOS中的對(duì)象內(nèi)存占用

總所周知,oc對(duì)象底層是由結(jié)構(gòu)體實(shí)現(xiàn)的,所以通過(guò)分析結(jié)構(gòu)體內(nèi)存占用情況可以更好的理解oc對(duì)象的內(nèi)存占用。

1.把OC對(duì)象編譯成結(jié)構(gòu)體

有如下代碼:

#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        
        Person *per = [[Person alloc] init];
        NSLog(@"per:%@",per);
        
    }
    return 0;
}

我們可以通過(guò)clang命名把.m文件編譯成.cpp文件,進(jìn)而可以清楚的看到為什么說(shuō)oc對(duì)象底層是結(jié)構(gòu)體實(shí)現(xiàn)。

//clang命令
clang -rewrite-objc main.m [-o 別名]

編譯后如下:

struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
};

2.結(jié)構(gòu)體內(nèi)存占用分析

2.1 常用數(shù)據(jù)類型內(nèi)存占用大小

我們都知道,在不同位數(shù)的編譯器環(huán)境下,數(shù)據(jù)類型不同其占用字節(jié)大小也不相同,區(qū)別如下:

  • 32位編譯器


    32位編譯器下.png
  • 64位編譯器

    64位編譯器下.png

2.2 結(jié)構(gòu)體內(nèi)存對(duì)齊規(guī)則

為了方便cpu更加快速地讀取存放在內(nèi)存中的數(shù)據(jù),內(nèi)存在存放數(shù)據(jù)時(shí)會(huì)按照一定的規(guī)則來(lái)排列,規(guī)則如下:

  • 數(shù)據(jù)成員對(duì)?規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第
    ?個(gè)數(shù)據(jù)成員放在offset為0的地?,以后每個(gè)數(shù)據(jù)成員存儲(chǔ)的起始位置要從該成員??或者成員的?成員??(只要該成員有?成員,?如說(shuō)是數(shù)組,結(jié)構(gòu)體等)的整數(shù)倍開(kāi)始(?如int為4字節(jié),則要從4的整數(shù)倍地址開(kāi)始存
    儲(chǔ)。
  • 結(jié)構(gòu)體作為成員:如果?個(gè)結(jié)構(gòu)?有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從
    其內(nèi)部最?元素??的整數(shù)倍地址開(kāi)始存儲(chǔ).(struct a?存有struct b,b?有char,int ,double等元素,那b應(yīng)該從8的整數(shù)倍開(kāi)始存儲(chǔ).)
  • 收尾?作:結(jié)構(gòu)體的總??,也就是sizeof的結(jié)果,.必須是其內(nèi)部最大成員的整數(shù)倍,不?的要補(bǔ)?。

2.3 內(nèi)存對(duì)齊計(jì)算

2.3.1 普通結(jié)構(gòu)體

例如有下面這樣一個(gè)結(jié)構(gòu)體,其內(nèi)存大小為24.分析如下:

struct AA{
    double a;   //[0,7]
    char b;     //[8]
    int c;      //[12,15]
    int d;      //[16,19]
};
printf("大小為:%d",sizeof(struct AA));//24

我們可以知道sizeof可以用來(lái)計(jì)算對(duì)象類型所占內(nèi)存大小,按照規(guī)則1我們可以將結(jié)構(gòu)體AA對(duì)象排列如下:


棧排列.png

我們可以看到,a是第一個(gè)成員且長(zhǎng)度大小為8,所以占位序號(hào)為[0,7];
b的長(zhǎng)度大小為1,所以占位序號(hào)為[8];c的長(zhǎng)度大小為4,但是根據(jù)規(guī)則一,這里c不能從序號(hào)9開(kāi)始,因?yàn)?不滿足對(duì)齊數(shù)(4)的整數(shù)倍,所以要從12開(kāi)始排列;同理d的占位序號(hào)為[16,19],那么整個(gè)結(jié)構(gòu)體大小為19+1=20字節(jié),又因?yàn)?0不滿足規(guī)則三,所以總大小應(yīng)該是最大長(zhǎng)度(8)的最小整數(shù)倍且不小于20字節(jié)的數(shù),即24.

2.3.2 嵌套結(jié)構(gòu)體

例如結(jié)構(gòu)體嵌套的情況:

struct BB{
    double a;       //[0,7]
    struct AA b;    //[8,31]
    char c;         //[32]
};
printf("BB:%lu\n",sizeof(struct BB));//40

思路分析:

  • a長(zhǎng)度為8且為首元素,所以占位序號(hào)為[0,7]
  • b為結(jié)構(gòu)體變量且長(zhǎng)度大小為24,根據(jù)規(guī)則二我們得出b不能從24開(kāi)始,所以b的占位序號(hào)為[8,31]
  • c的長(zhǎng)度為1,占位序號(hào)為[32],所以總大小為:32+1=33,然后33并不是結(jié)構(gòu)體BB的最大對(duì)齊數(shù)(8)的整數(shù)倍,因此BB大小為ceil(33/8.0)*8=40.

3.IOS中對(duì)象內(nèi)存大小

前面說(shuō)完了結(jié)構(gòu)體內(nèi)存占用大小的情況,下面說(shuō)說(shuō)OC對(duì)象占用大小和實(shí)際申請(qǐng)大小該怎么計(jì)算吧。
首頁(yè),我們往Person類中新增name,nickName,age等幾個(gè)屬性.

#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic,copy)NSString *name;
@property (nonatomic,copy)NSString *nickName;
@property (nonatomic,assign)unsigned int age;
@property (nonatomic,assign)double score;
@end

并賦值如下:

#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        
        Person *per = [[Person alloc] init];
        per.name = @"James";
        per.nickName = @"Potter";
        per.age = 18;
        NSLog(@"per:%@",per);
        NSLog(@"需要申請(qǐng)大小:%ld",class_getInstanceSize([per class]));//40
        NSLog(@"實(shí)際申請(qǐng)大小:%ld",malloc_size((__bridge const void *)per));//48
        
    }
    return 0;
}

因?yàn)镻erson有四個(gè)屬性:name和nickName是指針變量各占用8個(gè)字節(jié),age占用4個(gè)字節(jié),score占用8個(gè)字節(jié)。其次通過(guò)結(jié)構(gòu)體我們可以前面clang編譯我們可以看到,結(jié)構(gòu)體里面還有一個(gè)isa指針,所以Person類總大小為:8+8+8+4+8=36,對(duì)齊后應(yīng)該是最大長(zhǎng)度的整數(shù)倍,即為40.
然而為什么在實(shí)際申請(qǐng)內(nèi)存過(guò)程中是48呢?其實(shí)蘋果底層在申請(qǐng)內(nèi)存是是按照16字節(jié)來(lái)申請(qǐng)的.
通過(guò)objc4中class_getInstanceSize源碼分析

/** 
 * Returns the size of instances of a class.
 * 
 * @param cls A class object.
 * 
 * @return The size in bytes of instances of the class \e cls, or \c 0 if \e cls is \c Nil.
 */
OBJC_EXPORT size_t
class_getInstanceSize(Class _Nullable cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);


size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}


// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
    return word_align(unalignedInstanceSize());
}


static inline uint32_t word_align(uint32_t x) {
    //x+7 & (~7) --> 8字節(jié)對(duì)齊
    return (x + WORD_MASK) & ~WORD_MASK;
}


//其中 WORD_MASK 為
#   define WORD_MASK 7UL

實(shí)際申請(qǐng)內(nèi)存時(shí)instanceSize源碼分析

size_t instanceSize(size_t extraBytes) const {
    //編譯器快速計(jì)算內(nèi)存大小
    if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
        return cache.fastInstanceSize(extraBytes);
    }
    
    // 計(jì)算類中所有屬性的大小 + 額外的字節(jié)數(shù)0
    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    //如果size 小于 16,最小取16
    if (size < 16) size = 16;
    return size;
}

所以為什么蘋果要按照16字節(jié)對(duì)齊呢?

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

以上就是我對(duì)對(duì)象內(nèi)存大小占用情況的簡(jiǎn)單分析,歡迎各位大佬們點(diǎn)贊。

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

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