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

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

1.把OC對象編譯成結(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;
}

我們可以通過clang命名把.m文件編譯成.cpp文件,進而可以清楚的看到為什么說oc對象底層是結(jié)構(gòu)體實現(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)存對齊規(guī)則

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

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

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

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

例如有下面這樣一個結(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可以用來計算對象類型所占內(nèi)存大小,按照規(guī)則1我們可以將結(jié)構(gòu)體AA對象排列如下:


棧排列.png

我們可以看到,a是第一個成員且長度大小為8,所以占位序號為[0,7];
b的長度大小為1,所以占位序號為[8];c的長度大小為4,但是根據(jù)規(guī)則一,這里c不能從序號9開始,因為9不滿足對齊數(shù)(4)的整數(shù)倍,所以要從12開始排列;同理d的占位序號為[16,19],那么整個結(jié)構(gòu)體大小為19+1=20字節(jié),又因為20不滿足規(guī)則三,所以總大小應(yī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長度為8且為首元素,所以占位序號為[0,7]
  • b為結(jié)構(gòu)體變量且長度大小為24,根據(jù)規(guī)則二我們得出b不能從24開始,所以b的占位序號為[8,31]
  • c的長度為1,占位序號為[32],所以總大小為:32+1=33,然后33并不是結(jié)構(gòu)體BB的最大對齊數(shù)(8)的整數(shù)倍,因此BB大小為ceil(33/8.0)*8=40.

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

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

#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(@"需要申請大小:%ld",class_getInstanceSize([per class]));//40
        NSLog(@"實際申請大小:%ld",malloc_size((__bridge const void *)per));//48
        
    }
    return 0;
}

因為Person有四個屬性:name和nickName是指針變量各占用8個字節(jié),age占用4個字節(jié),score占用8個字節(jié)。其次通過結(jié)構(gòu)體我們可以前面clang編譯我們可以看到,結(jié)構(gòu)體里面還有一個isa指針,所以Person類總大小為:8+8+8+4+8=36,對齊后應(yīng)該是最大長度的整數(shù)倍,即為40.
然而為什么在實際申請內(nèi)存過程中是48呢?其實蘋果底層在申請內(nèi)存是是按照16字節(jié)來申請的.
通過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é)對齊
    return (x + WORD_MASK) & ~WORD_MASK;
}


//其中 WORD_MASK 為
#   define WORD_MASK 7UL

實際申請內(nèi)存時instanceSize源碼分析

size_t instanceSize(size_t extraBytes) const {
    //編譯器快速計算內(nèi)存大小
    if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
        return cache.fastInstanceSize(extraBytes);
    }
    
    // 計算類中所有屬性的大小 + 額外的字節(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é)對齊呢?

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

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

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

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