1.對象的內(nèi)存地址分布
首先創(chuàng)建一個Person類摆马,其中包含了name
臼闻、nickname
、age
囤采、c1
和c2
五個屬性述呐。此時創(chuàng)建一個person對象,并對其屬性進(jìn)行賦值蕉毯,然后打印person對象的內(nèi)存地址乓搬。
通常在調(diào)試窗口使用
p person
命令打印對象person的內(nèi)存地址。使用x/4gx
或x/8gx
+ 內(nèi)存地址
或$11
恕刘,(其中$11
為圖中person 類的標(biāo)識符 )可以來打印具體的地址下的內(nèi)存地址情況缤谎,如圖。
- 在使用
x/8gx
打印對象內(nèi)存地址的結(jié)果中褐着,冒號前面的是從對象的首地址坷澡,冒號后是其屬性的值,如果屬性未被賦值時含蓉,為0x0000000000000000
频敛; - 標(biāo)識
1
的0x0000600002d6fd20
地址是指對象person
的首地址项郊; - 標(biāo)識
2
的是當(dāng)前person對象的isa
指針,指向的是類斟赚; - 標(biāo)識
3
包含了age
着降,c1
和c2
三個屬性的值,在OC中為了節(jié)約內(nèi)存空間將基本數(shù)據(jù)類型的數(shù)據(jù)可以放在一條數(shù)據(jù)內(nèi)存中 - 開辟內(nèi)存時進(jìn)行了內(nèi)存重排拗军,從圖中
0x0000001200006261
在0x000000010e0c6038
和0x000000010e0c6058
前的原因
2.結(jié)構(gòu)體的內(nèi)存對齊
通過查看源碼可以看出任洞,OC對象的內(nèi)存對齊來自于結(jié)構(gòu)體的內(nèi)存,所以研究結(jié)構(gòu)體发侵。結(jié)構(gòu)體指針的大小為8字節(jié)交掏,結(jié)構(gòu)體的內(nèi)存大小取決于其內(nèi)部屬性。
struct Struct1 {
double a; // 8 0 1 2 3 4 5 6 7
char b; // 1 8
int c; // 4 12 13 14 15
short d; // 2 16 17 總計需要:17 總計開辟:24
} struct1;
struct Struct2 {
double a; // 8 0 1 2 3 4 5 6 7
int b; // 4 8 9 10 11
char c; // 1 12
short d; // 2 14 15 總計需要:15 總計開辟:16
struct Struct1 struct1;
} struct2;
打印如下:
2020-10-02 11:52:16.814642+0800 內(nèi)存對齊[2209:74659] 24-40
首先分析結(jié)構(gòu)體struct1
:
在64位架構(gòu)下刃鳄,按照定義計數(shù)從0位開始
-
double a
為8
個字節(jié)則為(0盅弛,1,2叔锐,3挪鹏,4,5愉烙,6讨盒,7)8
位 -
char b
為1
個字節(jié),根據(jù)連續(xù)性原則b
會從第8
位開始齿梁,所以b
占用第8
位催植; -
int c
為4
個字節(jié),根據(jù)內(nèi)存對齊的原則勺择,當(dāng)前屬性的開始位是其所占內(nèi)存大小的整數(shù)倍
,而b
占用了第8
位伦忠,第9
位不是4
的倍數(shù)省核,所以需要向后移動直到第12
位,占用了(12昆码,13气忠,14,15)
位 -
short d
為2
個字節(jié)赋咽,根據(jù)當(dāng)前屬性的開始位是其所占內(nèi)存大小的整數(shù)倍
原則旧噪,d
的占用了(16,17)
位 -
struct1
實際占用內(nèi)存17
字節(jié)脓匿,根據(jù)結(jié)構(gòu)體開辟內(nèi)存大小是其屬性最大值的整數(shù)倍
淘钟,并且要滿足大于或等于實際需要大小,因此struct1
的內(nèi)存大小為24
陪毡。
對于結(jié)構(gòu)體struct2
來說:
-
double a
為8
個字節(jié)則為(0米母,1勾扭,2,3铁瞒,4妙色,5,6慧耍,7)8
位 -
int b
為4
個字節(jié)身辨,根據(jù)內(nèi)存對齊的原則,當(dāng)前屬性的開始位是其所占內(nèi)存大小的整數(shù)倍
芍碧,所以b
占用了(8煌珊,9,10师枣,11)
位 -
char c
為1
個字節(jié)怪瓶,所以占用了第12
位 -
short d
為2
個字節(jié),根據(jù)當(dāng)前屬性的開始位是其所占內(nèi)存大小的整數(shù)倍
原則践美,d
占用了(14洗贰,15)位 - 由上面的
struct1
得到struct1
的內(nèi)存大小為24
字節(jié),根據(jù)如果結(jié)構(gòu)體中有結(jié)構(gòu)體作為屬性時陨倡,該結(jié)構(gòu)體的起始位需要是該結(jié)構(gòu)體中最大屬性值得整數(shù)倍
敛滋,此時struct1
正好從16
開始,并占用了24
字節(jié) - 由上可得出
struct2
的內(nèi)存大小為40
3. instanceSize方法分析
instanceSize
方法是在初始化alloc
方法中的方法_class_createInstanceFromZone
中調(diào)用的兴革,用來計算對象開辟的內(nèi)存大小绎晃。首先通過宏定義得fastPath()
來判斷是否有較大概率進(jìn)行快速路徑,此時使用的是gcc
中定義的__builtin_except(N, 1)
表示較大概率執(zhí)行杂曲,此時返回調(diào)用cache
中的方法fastInstanceSize
來計算內(nèi)存大小庶艾,在fastInstanceSize
中會走計算size_t size = _flags & FAST_CACHE_ALLOC_MASK
通過按位與
的計算方式將_flags
與宏定義的FAST_CACHE_ALLOC_MASK 0x1ff8
計算得出內(nèi)存大小,然后調(diào)用align16
方法擎勘,在align16
方法中使用(x+size_t(15) & ~size_t(15))
取16位以下為0的方式咱揍,進(jìn)行了按照16位的倍數(shù)的內(nèi)存對齊
-
_flags
是chass_rw_t
結(jié)構(gòu)體中的屬性,在初始化alloc
方法時系統(tǒng)回調(diào)用objc_class
結(jié)構(gòu)體中的setInfo
方法棚饵,并調(diào)用data->setFlags
方法為_flags
進(jìn)行了賦值 -
x+size_t(15) & ~size_t(15)
: 將x與15相加煤裙,然后再和15按位取反
的數(shù)進(jìn)行按位與
計算,示例如下, 令 x = 7:
x = 7 0000 0111
15 0000 1111
x + 15 0001 0110
~15 1111 0000
x + size_t(15) & ~size_t(15)
16 0001 0000
可以看出該算法是開辟的內(nèi)存使用實際開辟的內(nèi)存進(jìn)行16的倍數(shù)擴(kuò)容噪漾。
4. calloc 分析
- 在
malloc_zone_calloc
中返回的是void *
類型的指針ptr
硼砰,所以在該方法中重點為ptr
的賦值部分ptr = zone->calloc(zone, num_items, size)
,在該方法中進(jìn)入calloc
又會產(chǎn)生遞歸欣硼,通過研究此處實際調(diào)用default_zone_calloc
方法题翰。 - 在
default_zone_calloc
中return zone->calloc(zone, num_items, size)
此時又調(diào)用了一次zone
的calloc
方法,在實際當(dāng)中調(diào)用的則為nano_malloc
方法 - 在
nano_malloc
方法中
static void *nano_malloc(nanozone_t *nanozone, size_t size)
{
if (size <= NANO_MAX_SIZE) {
void *p = _nano_malloc_check_clear(nanozone, size, 0);
if (p) {
return p;
} else {
/* FALLTHROUGH to helper zone */
}
}
malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
return zone->malloc(zone, size);
}
重點是調(diào)用了_nano_malloc_check_clear
方法進(jìn)行對p
指針進(jìn)行賦值,在初始化時size 為 0遍愿,一定是小于NANO_MAX_SIZE
= 256的存淫,所以此處一定會返回p
。
- 在
_nano_malloc_check_clear
方法中沼填,重點在如下語句中
void *ptr;
size_t slot_key;
size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key); // Note slot_key is set here
mag_index_t mag_index = nano_mag_index(nanozone);
nano_meta_admin_t pMeta = &(nanozone->meta_data[mag_index][slot_key]);
ptr = OSAtomicDequeue(&(pMeta->slot_LIFO), offsetof(struct chained_block_s, next));
所以重點在一個slot_bytes
的計算桅咆,然后對 ptr
的賦值,而slot_bytes
的計算是重點坞笙,接下來研究segregated_size_to_fit
方法
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; // Historical behavior
}
k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
slot_bytes = k << SHIFT_NANO_QUANTUM; // multiply by power of two quanta size
*pKey = k - 1; // Zero-based!
return slot_bytes;
}
在代碼中 NANO_REGIME_QUANTA_SIZE
為 (1 << SHIFT_NANO_QUANTUM)
也就是16
岩饼,SHIFT_NANO_QUANTUM
為 4
,所以在初始化時,通過size = NANO_REGIME_QUANTA_SIZE
給 size
賦值為16
薛夜, (size + NANO_REGIME_QUANTA_SIZE -1) >> SHIFT_NANO_QUANTUM
是將低四位進(jìn)行抹零籍茧,slot_bytes = k << SHIFT_NAMO_QUANTUM
將k進(jìn)行左移四位,通過這兩步將高位保留低位抹零梯澜,從而保證為16
的倍數(shù)寞冯。