1.回顧之前
? 前面我們講過alloc的一些底層探索中缠导,在分配內(nèi)存的時候有涉及到內(nèi)存對齊的概念沦泌。instanceSize()中alignedInstanceSize()內(nèi)存分配粗略的講到了內(nèi)存對齊的概念瞄勾,下面我們來詳細了解一下
2.內(nèi)存對齊-初探
? 什么是內(nèi)存對齊?是否有很多問號弥激,剛接觸到這個概念的時候进陡,也是很疑惑。概念:編譯器在讀取內(nèi)存地址的時候秆撮,會按照一定的偏移量去讀人谋簟;比如在一個寫的struct里面定義一定變量职辨,里面變量大小都是4字節(jié)盗蟆,8字節(jié),16 字節(jié)的舒裤,而且sizeof()大小也不是里面定義大小一樣(大于里面定義的字節(jié)大性省)。
? 為什么呢腾供?下面??我們來說明一下原因
內(nèi)存對齊的原因
? 1.讀取效率:cpu數(shù)據(jù)訪問效率仆邓。一般平臺系統(tǒng)會從基數(shù)位地址開始讀取,那么需要兩個周期才能拼湊成32bit/64bit伴鳖。所以從效率上考慮系統(tǒng)會吧基數(shù)字節(jié)自動補齊成偶數(shù)位一個周期便可以讀取完成节值,提高了讀取效率
? 2.硬件原因:一部分平臺不能訪問任意地址上的數(shù)據(jù)的,需要訪問特定指定類型的數(shù)據(jù)榜聂,不然會出現(xiàn)異常(具體哪些平臺尚不了解搞疗,需從網(wǎng)上了解...)
內(nèi)存對齊規(guī)則
1)數(shù)據(jù)成員對其規(guī)則:結(jié)構(gòu)體(struct)或聯(lián)合體(union)的數(shù)據(jù)成員,第一個數(shù)據(jù)成員放在offset為0的地方须肆,以后每個數(shù)據(jù)成員存儲的起始位置要從該成員大小或者成員的子成員大心淠恕(只要該成員有子成員,比如數(shù)組豌汇、結(jié)構(gòu)體等)的整數(shù)倍開始幢炸。
? 總結(jié):int為4字節(jié),則要從4的整數(shù)倍開始存儲
2)結(jié)構(gòu)體作為成員:如果一個結(jié)構(gòu)體內(nèi)部包含其他結(jié)構(gòu)體成員拒贱,則結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開始存儲宛徊。
總結(jié):struct x中包含char ,int柜思,double岩调,float等元素,那應該從8(double字節(jié)大猩呐獭)的整數(shù)倍開始存儲
3)收尾工作:結(jié)構(gòu)體的總大小,也就是sizeof的結(jié)果缰揪,必須是其內(nèi)部最大成員的整數(shù)倍陨享,不足的需要補齊葱淳。在malloc中總是為16的倍數(shù)。
內(nèi)存對齊-實戰(zhàn)
舉個栗子1
struct StructOne {
char a; // 1字節(jié)
double b; // 8字節(jié)
int c; // 4字節(jié)
short d; // 2字節(jié)
} Struct1;
?
struct StructTwo {
double b; // 8字節(jié)
int c; // 4字節(jié)
char a; // 1字節(jié)
short d; // 2字節(jié)
} Struct2;
?
struct StructOThree {
double b; // 8字節(jié)
char a; // 1字節(jié)
int c; // 4字節(jié)
short d; // 2字節(jié)
} Struct3;
?
NSLog(@"%lu---%lu---%lu",sizeof(Struct1),sizeof(Struct2),sizeof(Struct3));
輸出結(jié)果為: 24—16—24
我們從內(nèi)存對齊原則看抛姑,上面三個結(jié)構(gòu)體在內(nèi)存棧中的分布應該是這樣的:
搜狐公眾號推送的一片文章-內(nèi)存布局(推薦閱讀赞厕,很詳細)
類對象內(nèi)存開辟
1.準備工作
LLDB調(diào)試知識:
①: x/4gx 對象
表示輸出4個16進制的8字節(jié)地址空間(x表示16進制,4表示4個定硝,g表示8字節(jié)為單位皿桑,等同于x/4xg 對象
)
②:po
與p
:p表示"expression"——打印對象指針;而po是"expression -O"——打印對象本身
③:Xcode查看內(nèi)存地址 debug->Debug Workflow->view memory
? 舉個栗子??
@interface WXPerson : NSObject
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) long height;
@property (nonatomic, assign) char c1;
@property (nonatomic, assign) float m_float;
@end
?
#import "WXPerson.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
?
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
WXPerson *p = [[WXPerson alloc] init];
p.name = @"Kaemi"; // NSString 8
p.age = 18; // int 4
p.height = 188; // long 8
p.c1 = 'a'; // char 1
p.m_float = 12.6; // float 4
NSLog(@"sizeof:%lu---申請內(nèi)存大小為:%lu——-系統(tǒng)開辟內(nèi)存大小為:%lu",sizeof([p class]),class_getInstanceSize([p class]),malloc_size((__bridge const void *)(p)));
}
return 0;
}
輸出結(jié)果為:sizeof:8---申請內(nèi)存大小為:40——-系統(tǒng)開辟內(nèi)存大小為:48
幾個知識點:sizeof() class_getInstanceSize() malloc_size() 是什么
sizeof:它是一個運算符蔬啡,在編譯時就可以獲取類型所占內(nèi)存的大小
class_getInstanceSize:依賴于<objc/runtime.h>
诲侮,返回創(chuàng)建一個實例對象所需內(nèi)存大小,不考慮malloc函數(shù)的話箱蟆,內(nèi)存對齊一般是以 8 字節(jié)對齊
malloc_size:依賴于<malloc/malloc.h>
沟绪,返回系統(tǒng)實際分配的內(nèi)存大小,在Mac空猜、iOS中的malloc函數(shù)分配的內(nèi)存大小總是 16 的倍數(shù)
那為啥class_getInstanceSize返回的值是40绽慈,malloc_size返回時48呢?下面??我們來分析一下
3.malloc源碼分析
內(nèi)存開辟辈毯,我們在
alloc
內(nèi)存開辟中還遺留了一個問題:obj = (id)calloc(1, size)
坝疼,之前我們用objc源碼無法斷點下手,現(xiàn)在我們可以用libmalloc
源碼來分析一下
在libmalloc源碼
中新建target谆沃,按照objc源碼
中的方式調(diào)用
void *p = calloc(1, 40);
斷點malloc_zone_calloc();
通過調(diào)試臺看一下具體實現(xiàn)
(lldb) p zone->calloc
(void *(*)(_malloc_zone_t *, size_t, size_t)) $0 = 0x000000010031cd14 (.dylib`default_zone_calloc at malloc.c:249)
確定default_zone_calloc钝凶,再搜索它的實現(xiàn)源碼
static void *
default_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
zone = runtime_default_zone();
return zone->calloc(zone, num_items, size);
}
繼續(xù)用lldb查看:
(lldb) p zone->calloc
(void *(*)(_malloc_zone_t *, size_t, size_t)) $0 = 0x000000010031e33f (.dylib`nano_calloc at nano_malloc.c:878)
再進去
最后我們會到這邊
/*
* #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_t k, slot_bytes;
if (0 == size) {
size = NANO_REGIME_QUANTA_SIZE;
}
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;
}
class_getInstanceSize 是對象對8字節(jié)對齊,根據(jù)內(nèi)存對齊原則可知分配了40大泄鼙小腿椎;
malloc_size
中的48是怎么來的。這里有多個size_t類
夭咬,斷點調(diào)試看了下的size
是我們傳進來的40啃炸,而slot_bytes
剛好是我們的目標48,
在經(jīng)過 (40 + 16 - 1) >> 4 << 4 操作后卓舵,結(jié)果為48南用,也就是16的整數(shù)倍——即16字節(jié)對齊
malloc_size
系統(tǒng)對對象做了16字節(jié)對齊
總結(jié)
對象的屬性是8字節(jié)對齊
對象是16字節(jié)對齊
因為內(nèi)存是連續(xù)的,通過 16 字節(jié)對齊規(guī)避風險和容錯掏湾,防止訪問溢出
同時裹虫,也提高了尋址訪問效率,也就是空間換時間