本篇探索依舊是基于objc
以及libmalloc
源碼晦攒,源碼下載及配置請(qǐng)參考本篇文章态贤。
一气笙、對(duì)齊原因:
1次企、平臺(tái)原因(移植原因):不是所有的硬件平臺(tái)都能訪問任意地址上的任意數(shù)據(jù)的行冰;某些硬件平臺(tái)只能在某些地址處取某些特定類型的數(shù)據(jù)婚肆,否則拋出硬件異常。
2柿冲、性能原因:數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對(duì)齊谭期。原因在于堵第,為了訪問未對(duì)齊的內(nèi)存吧凉,處理器需要作兩次內(nèi)存訪問;而對(duì)齊的內(nèi)存訪問僅需要一次訪問踏志。
(來源百度百科)
二阀捅、對(duì)齊規(guī)則
1、數(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ǔ))。
2伪朽、結(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ǔ))朴肺。
3、收尾工作:就夠提的總大小跃脊,也就是sizeof
的結(jié)果宇挫,必須是其內(nèi)部最大成員的整數(shù)倍,不足的要補(bǔ)齊酪术。
4器瘪、32位系統(tǒng)下4字節(jié)對(duì)齊,64位系統(tǒng)下8字節(jié)對(duì)齊绘雁。(本篇例子均默認(rèn)8字節(jié)對(duì)齊)
三橡疼、結(jié)合代碼分析
1、結(jié)構(gòu)體中內(nèi)存對(duì)齊分析
struct struct1 {
int a; // 4 字節(jié)
char b; // 1 字節(jié)
double c; // 8 字節(jié)
int *d; // 8 字節(jié)
}MyStruct1;
struct struct2 {
int a; // 4 字節(jié)
int *b; // 8 字節(jié)
char c; // 1 字節(jié)
struct struct1 myStruct; // 8字節(jié)
double d; // 8 字節(jié)
} MyStruct2;
1) 內(nèi)存分析:
括號(hào)內(nèi)為補(bǔ)齊位
MyStruct1
:
int a;
0-3 共4字節(jié)
char b;
4 共5字節(jié)
double c;
(5,6,7) 8-15 共16字節(jié)
int *d;
16-23 共24字節(jié)
8字節(jié)對(duì)齊后為24MyStruct2
:
int a;
0-3 共4字節(jié)
int *b
(4,5,6,7) 8-15字節(jié) 共16字節(jié)
char c
16 字節(jié) 共17字節(jié)
struct struct2 myStruct
: (17,18,19,20,21,22,23) 24+24 = 47 共48字節(jié)
double d
: 48-55 共56字節(jié)
8字節(jié)對(duì)齊后為56
2庐舟、對(duì)象及屬性中內(nèi)存對(duì)齊分析
Teacher *p = [Teacher alloc];
p.name = @"TRACER"; // 8
p.age = 18; // 4
p.height = 185; // 8
p.hobby = @"女"; // 8
// p.sex = 2;
p.ch1 = 'a'; // 1
p.ch2 = 'b'; // 1
1) 打印屬性以及對(duì)象分別所占的內(nèi)存空間欣除。
NSLog(@"%lu",class_getInstanceSize([p class])); // 40
NSLog(@"%lu",malloc_size((__bridge const void *)(p))); // 48
2) 屬性內(nèi)存優(yōu)化:
① 源碼:
// 64位下 WORD_MASK為7,也就是8字節(jié)對(duì)齊
static inline uint32_t word_align(uint32_t x) {
// (x + 7) >> 3 << 3
return (x + WORD_MASK) & ~WORD_MASK;
}
② LLDB
分析:
(lldb) x/6xg p
0x101044580: 0x001d80010000256d 0x0000001200006261
0x101044590: 0x00000001000020a0 0x00000000000000b9
0x1010445a0: 0x00000001000020c0 0x0000000000000000
(lldb) po 0x0000001200006261
10372493740046155960
(lldb) po 0x00000001000020a0
TRACER
(lldb) po 0x00000000000000b9
185
(lldb) po 0x00000001000020c0
女
- 第一個(gè)即
0x001d80010000256d
是isa
,先不管它挪略。 - 我們可以看出
age
和ch1
以及ch2
并沒有打印出我們想要的18历帚、a、b
杠娱,其實(shí)這里就是編譯器自身幫我們優(yōu)化的一個(gè)過程挽牢,那有沒有注意到0x0000001200006261
打印結(jié)果呢?下面我們分開來打印一下看看:
(lldb) po 0x00000012
18
(lldb) po 0x62
98
(lldb) po 0x61
97
97摊求、98感覺不太對(duì)禽拔?那還記得ASSIC編碼嗎?
屬性以8字節(jié)對(duì)齊,其中isa固定占8字節(jié)睹栖,最后一共占40字節(jié)
3) 對(duì)象內(nèi)存對(duì)齊分析:
上一篇 說到obj = (id)calloc(1, size);
進(jìn)一步的探索需要在libmalloc
源碼中進(jìn)行硫惕,下面我們來具體看一下。
為什么傳入40野来,會(huì)打印出48呢恼除?
void *p = calloc(1, 40);
(lldb) po malloc_size(p)
48
- 首先來到
malloc_zone_calloc
方法中,里面有一行至關(guān)重要的代碼ptr = zone->calloc(zone, num_items, size);
初始化并且返回了一個(gè)ptr
指針,斷點(diǎn)在此處打印如下:
(lldb) p zone->calloc
(void *(*)(_malloc_zone_t *, size_t, size_t)) $1 = 0x0000000100381a5f (.dylib`default_zone_calloc at malloc.c:249)
- 然后來到
default_zone_calloc
中梁只,會(huì)看到return zone->calloc(zone, num_items, size);
斷點(diǎn)在此處繼續(xù)打印如下:
(lldb) p zone->calloc
(void *(*)(_malloc_zone_t *, size_t, size_t)) $2 = 0x0000000100383042 (.dylib`nano_calloc at nano_malloc.c:884)
接著來到
nano_calloc
中缚柳,這個(gè)方法中核心代碼是void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1);
,進(jìn)行內(nèi)存申請(qǐng)搪锣,并且返回指針p
秋忙。_nano_malloc_check_clear
中找到這行size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key);
就是對(duì)象開啟內(nèi)存大小的算法:
#define SHIFT_NANO_QUANTUM 4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) // 16
static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
// size = 40
size_t k, slot_bytes;
if (0 == size) {
size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
}
// (size + 1<<4 -1) >> 4
k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
slot_bytes = k << SHIFT_NANO_QUANTUM;
*pKey = k - 1;
return slot_bytes;
}
以上我們可以看出對(duì)象是16字節(jié)對(duì)齊,避免發(fā)生內(nèi)存溢出/野指針的問題构舟。
四灰追、總結(jié)
1、屬性是以8字節(jié)對(duì)齊狗超,對(duì)象是以16字節(jié)對(duì)齊弹澎。
2、isa本身會(huì)占8字節(jié)努咐。
3苦蒿、聲明成員變量的順序不一致,會(huì)導(dǎo)致最終分配內(nèi)存大小的不同渗稍。
如有不當(dāng)佩迟,歡迎指正,感謝竿屹。