OC對(duì)象(二)-- 內(nèi)存對(duì)齊和calloc中的16字節(jié)對(duì)齊

OC對(duì)象(一)-- alloc和init底層到底在干嘛
OC對(duì)象(二)-- 內(nèi)存對(duì)齊和calloc中的16字節(jié)對(duì)齊
OC對(duì)象(三)-- isa結(jié)構(gòu)分析

內(nèi)存對(duì)齊初探


實(shí)例對(duì)象在內(nèi)存中的布局迄汛,是被系統(tǒng)優(yōu)化過(guò)的泉坐,不會(huì)按照屬性定義的順序在內(nèi)存中開(kāi)辟空間匆瓜。
舉個(gè)例子:
定義一個(gè)Person類喇聊,里面包括一些屬性

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nick;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) char ch1;
@property (nonatomic, assign) char ch2;
@end

@implementation Person
@end

初始化實(shí)例對(duì)象衣赶,給屬性進(jìn)行賦值:

Person *p = [Person alloc];
p.name = @"DragonetZ";
p.nick = @"DZ";
p.age = 18;
p.ch1 = 'a';
p.ch2 = 'z';
NSLog(@"p:%@", p);

使用lldb指令x/4gx p查看實(shí)例對(duì)象的內(nèi)存情況伍茄,p是實(shí)例對(duì)象的指針變量淹朋,打印結(jié)果如下:

(lldb) x/4gx p
0x600000683e20: 0x00000001061fb6c8 0x0000001200007a61
0x600000683e30: 0x00000001061f9018 0x00000001061f9038
  • 第一個(gè)值0x00000001061fb6c8:實(shí)例對(duì)象的isa
  • 第二個(gè)值0x0000001200007a61:里面存放著age偏塞、ch1唱蒸、ch2的值,都是使用十六進(jìn)制表示灸叼。0x12對(duì)應(yīng)十進(jìn)制18神汹。0x7a對(duì)應(yīng)的十進(jìn)制122,ASCII中對(duì)應(yīng)的就是‘z’古今,同理0x61對(duì)應(yīng)的97屁魏,就是‘a(chǎn)’
  • 第三個(gè)值0x00000001061f9018:就是name屬性值‘DragonetZ’
  • 第四個(gè)值0x00000001061f9038:就是nick屬性值‘DZ’

如圖中展示,內(nèi)存中的排序和類型屬性定義的順序不一致捉腥。

拓展-lldb命令解釋


上文用了lldb指令x/4gx p氓拼,這里做一個(gè)簡(jiǎn)單解釋:

  • p:讀取實(shí)例對(duì)象p的內(nèi)存。也就是讀取內(nèi)存的起始位置抵碟。
  • 第一個(gè)x:是memory read指令的簡(jiǎn)寫(xiě)桃漾,讀取內(nèi)存作用
  • 4g:從起始位置開(kāi)始,讀取4段
  • 第二個(gè)x:代表的是16機(jī)制的方式讀取拟逮,同理可以切換成其他進(jìn)制模式:‘o’代表八進(jìn)制撬统,‘t’代表二進(jìn)制,‘d’代表十進(jìn)制唱歧。

打印內(nèi)容:


  • 冒號(hào)左側(cè)宪摧,也就是圖中的紅色框中代表的是內(nèi)存地址粒竖,第一個(gè)地址也就是p的首地址颅崩。與po p的打印是相同過(guò)的柳爽。
  • 冒號(hào)右側(cè)著蟹,是地址中的值呵萨。

內(nèi)存對(duì)齊原則


OC中俭令,實(shí)例對(duì)象其實(shí)就是struct類型灰嫉,因此我們研究一下struct是如何進(jìn)行內(nèi)存對(duì)齊的

簡(jiǎn)單的小案例

struct Struct1 {
    double a;
    char b;
    int c;
    short d;
}stu1;

struct Struct2 {
    double a;
    int b;
    char c;
    short d;
}stu2;

NSLog(@"%lu - %lu", sizeof(stu1), sizeof(stu2));

定義兩個(gè)struct贴汪,每個(gè)struct中都有幾個(gè)不同類型的成員鹿寨,打印兩個(gè)struct的內(nèi)存占用情況庐氮。


stu1占用24個(gè)字節(jié)瞧柔,stu2占用16字節(jié)漆弄。

此處可以使用下圖來(lái)自己先計(jì)算一下:


基礎(chǔ)數(shù)據(jù)類型內(nèi)存占用表

原理知識(shí)點(diǎn)

  1. struct第一個(gè)數(shù)據(jù)成員,從偏移量offset的0位開(kāi)始造锅。后續(xù)的成員從自身的整數(shù)倍的偏移位置開(kāi)始撼唾。
  2. 計(jì)算出來(lái)的總大小,需要是最大成員的整數(shù)倍哥蔚。

用stu1解釋說(shuō)明:

struct Struct1 {
    double a;
    char b;
    int c;
    short d;
}stu1;
  • double a是第一個(gè)成員倒谷,占用8個(gè)字節(jié)蛛蒙,根據(jù)說(shuō)明,第一個(gè)成員offset是0渤愁,占用空間【0-7】
  • char b牵祟,占用1個(gè)字節(jié),offset是8抖格,而且開(kāi)始位置8是需要占用空間1的整數(shù)倍诺苹,所以可以存放【8】
  • int c,占用4個(gè)字節(jié)他挎,offset是9筝尾,開(kāi)始位置9不是需要占用空間4的整數(shù)倍,需要后移到整數(shù)倍12上存放办桨,占用的位置是【12-15】
  • short d筹淫,占用2個(gè)字節(jié),offset是16呢撞,16是2的整數(shù)倍损姜,占用【16-17】
  • 占用【0-17】共18個(gè)字節(jié),成員中最大的是double殊霞,占8字節(jié)摧阅,所以取8的整數(shù)倍,就是24字節(jié)绷蹲。

再來(lái)看看stu2:

struct Struct2 {
    double a;
    int b;
    char c;
    short d;
}stu2;
  • double a棒卷,占用【0-7】
  • int b,【8-11】祝钢,因?yàn)?是4的整數(shù)倍比规。
  • char c,【12】
  • shot d拦英,【14-15】蜒什,因?yàn)殚_(kāi)始位置13不是2的整數(shù)倍,因此從14開(kāi)始
  • 占用【0-15】疤估,共16字節(jié)灾常,最大成員double是8字節(jié),取8整數(shù)倍铃拇,正好是16

擴(kuò)展-struct嵌套

如果struct a中有另一個(gè)struct b作為它的成員钞瀑,那么偏移量offset就取struct b中最大成員的整數(shù)倍開(kāi)始

struct Struct2 {
    double a;
    int b;
    char c;
    short d;
}stu2;

struct Struct3 {
    char a;
    short b;
    struct Struct2 c;
}stu3;

struct Struct2前面分析結(jié)構(gòu):占用16個(gè)字節(jié),最大成員是8字節(jié)
分析stu3

  • char a:【0】
  • short b:【2-3】
  • struct Struct2 c:找到8的整數(shù)倍作為開(kāi)始位慷荔,【8-23】
  • 取最大成員8的整數(shù)倍雕什,也就是24

OC底層的優(yōu)化

上面的例子中stu1和stu2兩個(gè)struct可以說(shuō)是差不多,但是一個(gè)占用24,一個(gè)占用16监徘。但是我們?cè)贠C類的時(shí)候晋修,屬性順序是不受影響的。說(shuō)明蘋(píng)果底層是對(duì)我們內(nèi)存開(kāi)辟進(jìn)行優(yōu)化過(guò)的凰盔。這里可以通過(guò)示例對(duì)象內(nèi)存打印中可以發(fā)現(xiàn)墓卦,文章開(kāi)始的例子中:


Person類中屬性age、ch1户敬、ch2的值都存放在內(nèi)存中第二個(gè)位置(0x0000001200007a61)中落剪。

calloc


之前文章alloc和init底層到底在干嘛!中分析了alloc流程尿庐,先是用16字節(jié)對(duì)齊的方式計(jì)算出size忠怖,然后調(diào)用calloc函數(shù)來(lái)開(kāi)辟內(nèi)存空間

//16字節(jié)對(duì)齊獲取到size
size = cls->instanceSize(extraBytes);

//根據(jù)size開(kāi)辟內(nèi)存空間
obj = (id)calloc(1, size);

此時(shí)產(chǎn)生一個(gè)問(wèn)題,如果傳入的size沒(méi)有進(jìn)行16字節(jié)對(duì)齊抄瑟,也就是說(shuō)傳入的不是16字節(jié)的倍數(shù)凡泣,會(huì)是什么情況?

測(cè)試代碼

#import <malloc/malloc.h>

void *temp = calloc(1, 40);
NSLog(@"%lu", malloc_size(temp));

調(diào)用calloc方法皮假,第二個(gè)參數(shù)傳入40鞋拟,注意這個(gè)值不是16的倍數(shù)。
運(yùn)行結(jié)果:


通過(guò)結(jié)果可以看出惹资,calloc里面也會(huì)進(jìn)行16字節(jié)對(duì)齊贺纲,接下來(lái)我們來(lái)找找這16字節(jié)對(duì)齊的代碼。

查看源碼

首先先看看calloc函數(shù)在哪個(gè)源碼中褪测,用?+鼠標(biāo)左鍵猴誊,屬于malloc源碼中


下載源碼libmalloc-283.100.6

源碼中大致的流程,如圖:


static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
    size_t k, slot_bytes;

    if (0 == size) {
        size = NANO_REGIME_QUANTA_SIZE; // 16
    }
    k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // (size + 16 -1)右移4位 SHIFT_NANO_QUANTUM=4
    slot_bytes = k << SHIFT_NANO_QUANTUM;                           // 左移4位
    *pKey = k - 1;                                                  // Zero-based!

    return slot_bytes;
}

這個(gè)函數(shù)就是16字節(jié)對(duì)齊的函數(shù):

  • 先判斷size如果等于0侮措,就給一個(gè)默認(rèn)值16懈叹,NANO_REGIME_QUANTA_SIZE是個(gè)宏。
  • 用size進(jìn)行計(jì)算萝毛,size+16-1项阴,將結(jié)果右移4位滑黔。然后在左移4位笆包。目的是將低4位抹零。通過(guò)左右位移4位略荡,來(lái)實(shí)現(xiàn)16字節(jié)對(duì)齊庵佣。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市汛兜,隨后出現(xiàn)的幾起案子巴粪,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肛根,死亡現(xiàn)場(chǎng)離奇詭異辫塌,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)派哲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)臼氨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人芭届,你說(shuō)我怎么就攤上這事储矩。” “怎么了褂乍?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵持隧,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我逃片,道長(zhǎng)屡拨,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任褥实,我火速辦了婚禮洁仗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘性锭。我一直安慰自己赠潦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布草冈。 她就那樣靜靜地躺著她奥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怎棱。 梳的紋絲不亂的頭發(fā)上哩俭,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音拳恋,去河邊找鬼凡资。 笑死,一個(gè)胖子當(dāng)著我的面吹牛谬运,可吹牛的內(nèi)容都是我干的隙赁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼梆暖,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼伞访!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起轰驳,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤厚掷,失蹤者是張志新(化名)和其女友劉穎弟灼,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體冒黑,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡田绑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抡爹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辛馆。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖豁延,靈堂內(nèi)的尸體忽然破棺而出昙篙,到底是詐尸還是另有隱情,我是刑警寧澤诱咏,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布苔可,位于F島的核電站,受9級(jí)特大地震影響袋狞,放射性物質(zhì)發(fā)生泄漏焚辅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一苟鸯、第九天 我趴在偏房一處隱蔽的房頂上張望同蜻。 院中可真熱鬧,春花似錦早处、人聲如沸湾蔓。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)默责。三九已至,卻和暖如春咸包,著一層夾襖步出監(jiān)牢的瞬間桃序,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工烂瘫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留媒熊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓坟比,卻偏偏與公主長(zhǎng)得像芦鳍,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子温算,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348