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

總所周知,oc對(duì)象底層是由結(jié)構(gòu)體實(shí)現(xiàn)的,所以通過分析結(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;
}

我們可以通過clang命名把.m文件編譯成.cpp文件,進(jìn)而可以清楚的看到為什么說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ī)則來排列,規(guī)則如下:

  • 數(shù)據(jù)成員對(duì)?規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員欲诺,第
    ?個(gè)數(shù)據(jù)成員放在offset為0的地?抄谐,以后每個(gè)數(shù)據(jù)成員存儲(chǔ)的起始位置要從該成員??或者成員的?成員??(只要該成員有?成員,?如說是數(shù)組瞧栗,結(jié)構(gòu)體等)的整數(shù)倍開始(?如int為4字節(jié),則要從4的整數(shù)倍地址開始存
    儲(chǔ)斯稳。
  • 結(jié)構(gòu)體作為成員:如果?個(gè)結(jié)構(gòu)?有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從
    其內(nèi)部最?元素??的整數(shù)倍地址開始存儲(chǔ).(struct a?存有struct b,b?有char,int ,double等元素,那b應(yīng)該從8的整數(shù)倍開始存儲(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可以用來計(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開始竖幔,因?yàn)?不滿足對(duì)齊數(shù)(4)的整數(shù)倍板乙,所以要從12開始排列;同理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開始放接,所以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)存大小

前面說完了結(jié)構(gòu)體內(nèi)存占用大小的情況纠脾,下面說說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é)右核。其次通過結(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)存過程中是48呢贺喝?其實(shí)蘋果底層在申請(qǐng)內(nèi)存是是按照16字節(jié)來申請(qǐng)的.
通過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的性能殖侵,所以可以通過減少存取次數(shù)來降低cpu的開銷贸呢。
  • 由于在一個(gè)對(duì)象中,第一個(gè)屬性isa占8字節(jié)拢军,當(dāng)然一個(gè)對(duì)象肯定還有其他屬性楞陷,當(dāng)無屬性時(shí),會(huì)預(yù)留8字節(jié)茉唉,即16字節(jié)對(duì)齊固蛾,如果不預(yù)留结执,相當(dāng)于這個(gè)對(duì)象的isa和其他對(duì)象的isa緊挨著,容易造成訪問混亂艾凯。
  • 16字節(jié)對(duì)齊后献幔,可以加快CPU讀取速度,同時(shí)使訪問更安全趾诗,不會(huì)產(chǎn)生訪問混亂的情況

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末恃泪,一起剝皮案震驚了整個(gè)濱河市郑兴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贝乎,老刑警劉巖杈笔,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異糕非,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)球榆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門朽肥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人持钉,你說我怎么就攤上這事衡招。” “怎么了每强?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵始腾,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我空执,道長(zhǎng)浪箭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任辨绊,我火速辦了婚禮奶栖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘门坷。我一直安慰自己宣鄙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布默蚌。 她就那樣靜靜地躺著冻晤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绸吸。 梳的紋絲不亂的頭發(fā)上鼻弧,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天设江,我揣著相機(jī)與錄音,去河邊找鬼温数。 笑死绣硝,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的撑刺。 我是一名探鬼主播鹉胖,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼够傍!你這毒婦竟也來了甫菠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤冕屯,失蹤者是張志新(化名)和其女友劉穎寂诱,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體安聘,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡痰洒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了浴韭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丘喻。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖念颈,靈堂內(nèi)的尸體忽然破棺而出泉粉,到底是詐尸還是另有隱情,我是刑警寧澤榴芳,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布嗡靡,位于F島的核電站,受9級(jí)特大地震影響窟感,放射性物質(zhì)發(fā)生泄漏讨彼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一柿祈、第九天 我趴在偏房一處隱蔽的房頂上張望点骑。 院中可真熱鬧,春花似錦谍夭、人聲如沸黑滴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)袁辈。三九已至,卻和暖如春珠漂,著一層夾襖步出監(jiān)牢的瞬間晚缩,已是汗流浹背尾膊。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留荞彼,地道東北人冈敛。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像鸣皂,于是被迫代替她去往敵國(guó)和親抓谴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354