首先從源碼(基于779.1)調(diào)試追蹤一下alloc的流程。

大致如上圖所示
接下來從一道經(jīng)典面試題開個頭
一個NSObject對象占用多少內(nèi)存 答:系統(tǒng)分配16字節(jié),實際利用8字節(jié)
- NSObject 只有一個成員變量
isa指針 ,arm64架構(gòu)后 可以追尋到一個isa_t類型的聯(lián)合體(union)結(jié)構(gòu)
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
絕大多數(shù)情況下,蘋果采用了優(yōu)化的isa策略,即isa_t類型不是class 而是struct

根據(jù)它的位域來看 占用64位,即8字節(jié)。各位的含義這里就先不說了
在64位里指針占8字節(jié),看起來一個
NSObject對象 8字節(jié) 就行了,而不是16字節(jié),這就需要繼續(xù)探索了。
回到上面的alloc流程圖
我們主要來看看·cls->instanceSize(計算開辟內(nèi)存大小) 和(id)calloc(1, size) (申請開辟內(nèi)存,返回地址指針)
先看看cls->instanceSize 相關(guān)的源碼
size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
///都走快速計算 ,在runtime入口函數(shù)里,`map_images`內(nèi)的`realizeClassWithoutSwift` 的`setInstanceSize` 做cache的setFastInstanceSize
return cache.fastInstanceSize(extraBytes);
}
/// 不走下面了
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
///__builtin_constant_p(x) : 如果x的值在編譯時能確定,那么該函數(shù)返回值為1.
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
/// 即得到`setFastInstanceSize`里 (word_align(newSize) + FAST_CACHE_ALLOC_DELTA16)
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
/// 減去 FAST_CACHE_ALLOC_DELTA16 得到 word_align(newSize),再進(jìn)行16字節(jié)對齊
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
///16字節(jié)對齊的算法
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
void setFastInstanceSize(size_t newSize)
{
// Set during realization or construction only. No locking needed.
uint16_t newBits = _flags & ~FAST_CACHE_ALLOC_MASK;
uint16_t sizeBits;
// Adding FAST_CACHE_ALLOC_DELTA16 allows for FAST_CACHE_ALLOC_MASK16
// to yield the proper 16byte aligned allocation size with a single mask
sizeBits = word_align(newSize) + FAST_CACHE_ALLOC_DELTA16;
sizeBits &= FAST_CACHE_ALLOC_MASK;
if (newSize <= sizeBits) {
newBits |= sizeBits;
}
//uint16_t a = ((_flags & ~FAST_CACHE_ALLOC_MASK)|(((word_align(newSize) + FAST_CACHE_ALLOC_DELTA16))&FAST_CACHE_ALLOC_MASK))&FAST_CACHE_ALLOC_MASK;
_flags = newBits;
}
通過debug發(fā)現(xiàn) ,instanceSize只會走fastInstanceSize, 而這個我看了一下,在runtime入口函數(shù)里,map_images內(nèi)的realizeClassWithoutSwift 的setInstanceSize 做了cache的setFastInstanceSize。
所以我們接著往下看fastInstanceSize 走else 判斷
-
size_t size = _flags & FAST_CACHE_ALLOC_MASK;相當(dāng)于獲取到得到setFastInstanceSize里(word_align(newSize) + FAST_CACHE_ALLOC_DELTA16),可以在setFastInstanceSize用_flags & FAST_CACHE_ALLOC_MASK驗證一下。 -
align16(size + extra - FAST_CACHE_ALLOC_DELTA16)參數(shù)減去了FAST_CACHE_ALLOC_DELTA16,因為在setFastInstanceSize加了setFastInstanceSize, 這樣得到的就是word_align(newSize)。
那么看一下word_align
# define WORD_MASK 7UL ( 0000 0000 0000 0111)
static inline size_t word_align(size_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
~WORD_MASK:1111 1111 1111 1000
(x + WORD_MASK) & ~WORD_MASK即把(x+7) 低三位清0,結(jié)果將是8的倍數(shù),與16字節(jié)對齊算法一樣。
那么
#define FAST_CACHE_ALLOC_DELTA16 0x0008
sizeBits = word_align(newSize) + FAST_CACHE_ALLOC_DELTA16;
FAST_CACHE_ALLOC_DELTA16等于8, 因此得到的sizeBits=8的倍數(shù)+8,至少也是16。
- 所以
size_t size = _flags & FAST_CACHE_ALLOC_MASK;獲取到的size>=16且是8的倍數(shù),再通過align16(size + extra - FAST_CACHE_ALLOC_DELTA16);進(jìn)行16字節(jié)對齊 -
(x + size_t(15)) & ~size_t(15);這里的x即word_align(newSize)
下面通俗的理解一下
15取反 1111 1111 1111 0000 = 1*2^15 +... 1*2^4
與15取反 相與 前四位為0 即抹掉前4位 只要是大于15的數(shù)與它相與必定是16的倍數(shù)
x 默認(rèn) + 15 ,如果x小于16 那么+15也是大于16了,自然也是返回16
舉個例子:
x = 8
(x + size_t(15)) & ~size_t(15)
15 二進(jìn)制 :0000 0000 0000 1111
~size_t(15) : 15取反 1111 1111 1111 0000
x + size_t(15) = 8+15 = 23 0000 0000 0001 0111
23&~15 : 0000 0000 0001 0000 =1*2^4 = 16
所以cls->instanceSize得到的size是16的倍數(shù)
接下來看看具體開辟內(nèi)存空間的方法calloc
這部分源碼需要去libmalloc里面看了,可以在mallco.c里找到實現(xiàn)
void *
calloc(size_t num_items, size_t size)
{
return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);
}
MALLOC_NOINLINE
static void *
_malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size,
malloc_zone_options_t mzo)
{
MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);
void *ptr;
if (malloc_check_start) {
internal_check();
}
ptr = zone->calloc(zone, num_items, size);
if (os_unlikely(malloc_logger)) {
malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone,
(uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0);
}
MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr);
if (os_unlikely(ptr == NULL)) {
malloc_set_errno_fast(mzo, ENOMEM);
}
return ptr;
}
到這里ptr = zone->calloc(zone, num_items, size);,單看源碼無法繼續(xù)往下了,因此編譯源碼來debug。
-
main函數(shù)calloc一下void *p = calloc(1, 8); - 在
ptr = zone->calloc(zone, num_items, size);打上斷點,lldb輸入s指令下一步,發(fā)現(xiàn)進(jìn)入default_zone_calloc
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);
}
- 又無法跳轉(zhuǎn),還是斷住,
s,發(fā)現(xiàn)進(jìn)入nano_calloc
static void *
nano_calloc(nanozone_t *nanozone, size_t num_items, size_t size)
{
size_t total_bytes;
if (calloc_get_size(num_items, size, 0, &total_bytes)) {
return NULL;
}
printf(&total_bytes);
if (total_bytes <= NANO_MAX_SIZE) {
void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1);
if (p) {
printf("1111\n");
return p;
} else {
/* FALLTHROUGH to helper zone */
}
}
malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
return zone->calloc(zone, 1, total_bytes);
}
- 調(diào)試?yán)^續(xù)往下看,進(jìn)入了
void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1);
調(diào)試看到這里的total_bytes就是傳進(jìn)來的size(通過這一步calloc_get_size(num_items, size, 0, &total_bytes)操作) - 而最終
_nano_malloc_check_clear里面有一個重要方法size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key);
通過調(diào)試發(fā)現(xiàn)slot_bytes即是分配的大小
static void *
_nano_malloc_check_clear(nanozone_t *nanozone, size_t size, boolean_t cleared_requested)
{ /// 部分源碼
MALLOC_TRACE(TRACE_nano_malloc, (uintptr_t)nanozone, size, cleared_requested, 0);
printf(&size);
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));
}
而最終的segregated_size_to_fit則又是做了一次16字節(jié)對齊
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;
}
可以看到當(dāng)size=0的時候,size=NANO_REGIME_QUANTA_SIZE ,如下宏,1左移4位 即 0000 0000 0001 0000 即16。
#define SHIFT_NANO_QUANTUM 4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) // 16
size!=0是進(jìn)行下面的操作
k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM;
slot_bytes = k << SHIFT_NANO_QUANTUM;
相當(dāng)于k = (size+16-1) >> 4; slot_bytes = k <<4; 相當(dāng)于低四位清0, 和上面align16效果一樣,得到的將是16的倍數(shù)。
例子:
size=8
k = (8+16-1) >>4 即23>>4
23: 0000 0000 0001 0111
右移4位 :0000 0000 0000 0001
再左移4位:0000 0000 0001 0000
即低四位清0 得16
由此可見,目前版本中instanceSize和calloc都做了內(nèi)存的16字節(jié)對齊的保證。
最后實踐來看看sizeof , class_getInstanceSize , malloc_size 區(qū)別
struct NSObject_IMPL {
Class isa;
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS; // 8字節(jié)
int _age; // 4字節(jié)
};// 16字節(jié)
/// 結(jié)構(gòu)體內(nèi)存對齊 大小必須是最大成員大小的倍數(shù)
struct Student_IMPL {
struct Person_IMPL Person_IVARS; // 16 字節(jié)
int _no; // 4字節(jié)
};// 16字節(jié)
@interface Person : NSObject
{
@public
int _age; // 4字節(jié)
}
@end
@implementation Person
@end
@interface Student : Person
{
@public
int _no;// 4字節(jié)
}
@end
@implementation Student
@end
如上Person和Student及其結(jié)構(gòu)體本質(zhì)
先各自打印出結(jié)果
NSObject *obj = [[NSObject alloc]init];
NSLog(@"sizeof obj: %zd",sizeof(struct NSObject_IMPL));
NSLog(@"class_getInstanceSize obj: %zd",class_getInstanceSize([NSObject class]));
NSLog(@"malloc_size obj: %zd",malloc_size((__bridge const void *)(obj)));
Person *person = [[Person alloc]init];
person->_age = 4;
Student *student = [[Student alloc]init];
student->_age = 3;
student->_no = 5;
NSLog(@"sizeof person: %zd",sizeof(struct Person_IMPL));
NSLog(@"class_getInstanceSize person: %zd",class_getInstanceSize([Person class]));
NSLog(@"malloc_size person: %zd",malloc_size((__bridge const void *)(person)));
NSLog(@"sizeof student struct: %zd",sizeof(struct Student_IMPL));
NSLog(@"sizeof student: %zd",sizeof(student));
NSLog(@"class_getInstanceSize studetn: %zd",class_getInstanceSize([Student class]));
NSLog(@"malloc_size studetn: %zd",malloc_size((__bridge const void *)(student)));

1. sizeof
- 是一個運算符,傳進(jìn)來的是類型,用來計算這個類型占多大內(nèi)存,這個在 編譯器編譯階段 就會確定大小。
比如sizeof(student),student即指針類型,占8個字節(jié),不管你是傳student或者person都是8
上面結(jié)果有一個sizeof(struct Student_IMPL)為24 , 按理Student_IMPL結(jié)構(gòu)體內(nèi)應(yīng)該是16+4=20,這是因為結(jié)構(gòu)體內(nèi)存對齊,大小必須是最大成員大小的倍數(shù),即isa占用的8的倍數(shù)。
2. class_getInstanceSize
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());
}
看注釋是 成員變量的大小 ,word_align 是8字節(jié)對齊的 , 所以class_getInstanceSize計算出來的大小是8字節(jié)對齊的。
3. malloc_size
- 計算對象實際分配的內(nèi)存大小 即上面分析的
segregated_size_to_fit,是16字節(jié)對齊的。
追蹤malloc_size的源碼 最終追蹤到
static MALLOC_INLINE size_t
__nano_vet_and_size_inner(nanozone_t *nanozone, const void *ptr, boolean_t inner)
{
// Extracts the size of the block in bytes. Checks for a plausible ptr.
nano_blk_addr_t p; // the compiler holds this in a register
nano_meta_admin_t pMeta;
p.addr = (uint64_t)ptr; // Begin the dissection of ptr
if (NANOZONE_SIGNATURE != p.fields.nano_signature) {
return 0;
}
if (nano_common_max_magazines <= p.fields.nano_mag_index) {
return 0;
}
if (!inner && p.fields.nano_offset & NANO_QUANTA_MASK) { // stray low-order bits?
return 0;
}
pMeta = &(nanozone->meta_data[p.fields.nano_mag_index][p.fields.nano_slot]);
if ((void *)(pMeta->slot_bump_addr) <= ptr) {
return 0; // Beyond what's ever been allocated!
}
if (!inner && ((p.fields.nano_offset % pMeta->slot_bytes) != 0)) {
return 0; // Not an exact multiple of the block size for this slot
}
printf("22222");
return pMeta->slot_bytes;
}
返回的是pMeta->slot_bytes,而這個pMeta->slot_bytes 在開辟空間時的_nano_malloc_check_clear里 可以看到蛛絲馬跡。
ptr = segregated_next_block(nanozone, pMeta, slot_bytes, mag_index);
最終走到segregated_band_grow里面有一段賦值
pMeta->slot_bytes = (unsigned int)slot_bytes;
所以malloc_size 即是經(jīng)過segregated_size_to_fit進(jìn)行16字節(jié)對齊的slot_bytes。
如有錯誤,請大佬糾正。