前話:
在了解內(nèi)存對(duì)齊之前先了解一下各數(shù)據(jù)類型在內(nèi)存中的大小,目前我們比較常用的是64位系統(tǒng),所以我們的研究對(duì)象統(tǒng)一采用64位的大小作為參考。
一. 如何獲取內(nèi)存的大小
獲取NSObject對(duì)象的內(nèi)存大小,需要用到以下幾個(gè)函數(shù):
- 1.class_getInstanceSize
- 2.malloc_size
- 3.sizeOf
我們先來(lái)一段代碼,然后調(diào)用上面的幾函數(shù),看一下結(jié)果
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
NSLog(@"class_getInstanceSize = %zd", class_getInstanceSize([NSObject class]));
NSLog(@"malloc_size = %zd", malloc_size((__bridge const void *)(obj)));
NSLog(@"sizeOf = %zd", sizeof(obj));
}
return 0;
}
控制臺(tái)打印如下:
class_getInstanceSize = 8
malloc_size = 16
sizeOf = 8
結(jié)果是 大小都不一樣,為什么呢?我們帶著疑問(wèn)來(lái)對(duì)上面幾個(gè)函數(shù)進(jìn)行分析一下吧。
1.1 class_getInstanceSize
我們先通過(guò)源碼來(lái)看一下class_getInstanceSize,實(shí)際調(diào)用的是alignedInstanceSize
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() {
return word_align(unalignedInstanceSize());
}
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() {
assert(isRealized());
return data()->ro->instanceSize;
}
從以上看出unalignedInstanceSize中指出返回的是類的屬性的大小總和,然后進(jìn)行字節(jié)對(duì)齊word_align, 其中WORD_MASK為7UL,即按8位補(bǔ)齊。
#ifdef __LP64__
# define WORD_SHIFT 3UL
# define WORD_MASK 7UL
# define WORD_BITS 64
#else
# define WORD_SHIFT 2UL
# define WORD_MASK 3UL
# define WORD_BITS 32
#endif
static inline uint32_t word_align(uint32_t x) {
//以下以 uint32_t x = 8 舉例,其中 WORD_MASK = 7UL
//第一種算法 ,15 & ~7
// 7+8 = 15
// 0000 1111 // 代表15
// 0000 1000 // ~7
//& //與操作
// 0000 1000 8 //結(jié)果是8
//第二種算法 ,位移3位
// 0000 1111 // 代表15
// 0000 1111 >> 3 // 為0000 0001
// 0000 0001 << 3 // 為0000 1000 //即 8
// (x + 7) >> 3 << 3
return (x + WORD_MASK) & ~WORD_MASK;
}
static inline size_t word_align(size_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
小 結(jié):class_getInstanceSize依賴于<objc/runtime.h>,返回創(chuàng)建一個(gè)實(shí)例對(duì)象所需內(nèi)存大小。就是獲取對(duì)象的全部屬性的大小總和,然后按8位對(duì)齊獲得,不足8位補(bǔ)齊8位。
?疑問(wèn)點(diǎn):目前例子中的對(duì)象實(shí)例化,但是沒(méi)有屬性,但是為什么還是占位8字節(jié)。
答:該8字節(jié)是對(duì)象的實(shí)例化后isa的大小8字節(jié)。(后續(xù)再詳細(xì)探討)
1.2.malloc_size
這個(gè)函數(shù)主要獲取系統(tǒng)實(shí)際分配的內(nèi)存大小,具體的底層實(shí)現(xiàn)也可以在源碼libmalloc找到,依賴于<objc/runtime.h>,具體如下:
extern size_t malloc_size(const void *ptr);
size_t
malloc_size(const void *ptr)
{
size_t size = 0;
if (!ptr) {
return size;
}
(void)find_registered_zone(ptr, &size);
return size;
}
核心的方法是find_registered_zone,具體如下:
static inline malloc_zone_t *find_registered_zone(const void *, size_t *) __attribute__((always_inline));
static inline malloc_zone_t *
find_registered_zone(const void *ptr, size_t *returned_size)
{
// Returns a zone which contains ptr, else NULL
if (0 == malloc_num_zones) {
if (returned_size) {
*returned_size = 0;
}
return NULL;
}
// first look in the lite zone
if (lite_zone) {
malloc_zone_t *zone = lite_zone;
size_t size = zone->size(zone, ptr);
if (size) { // Claimed by this zone?
if (returned_size) {
*returned_size = size;
}
// Return the virtual default zone instead of the lite zone - see <rdar://problem/24994311>
return default_zone;
}
}
// The default zone is registered in malloc_zones[0]. There's no danger that it will ever be unregistered.
// So don't advance the FRZ counter yet.
malloc_zone_t *zone = malloc_zones[0];
size_t size = zone->size(zone, ptr);
if (size) { // Claimed by this zone?
if (returned_size) {
*returned_size = size;
}
// Asan and others replace the zone at position 0 with their own zone.
// In that case just return that zone as they need this information.
// Otherwise return the virtual default zone, not the actual zone in position 0.
if (!has_default_zone0()) {
return zone;
} else {
return default_zone;
}
}
int32_t volatile *pFRZCounter = pFRZCounterLive; // Capture pointer to the counter of the moment
OSAtomicIncrement32Barrier(pFRZCounter); // Advance this counter -- our thread is in FRZ
unsigned index;
int32_t limit = *(int32_t volatile *)&malloc_num_zones;
malloc_zone_t **zones = &malloc_zones[1];
// From this point on, FRZ is accessing the malloc_zones[] array without locking
// in order to avoid contention on common operations (such as non-default-zone free()).
// In order to ensure that this is actually safe to do, register/unregister take care
// to:
//
// 1. Register ensures that newly inserted pointers in malloc_zones[] are visible
// when malloc_num_zones is incremented. At the moment, we're relying on that store
// ordering to work without taking additional steps here to ensure load memory
// ordering.
//
// 2. Unregister waits for all readers in FRZ to complete their iteration before it
// returns from the unregister call (during which, even unregistered zone pointers
// are still valid). It also ensures that all the pointers in the zones array are
// valid until it returns, so that a stale value in limit is not dangerous.
for (index = 1; index < limit; ++index, ++zones) {
zone = *zones;
size = zone->size(zone, ptr);
if (size) { // Claimed by this zone?
goto out;
}
}
// Unclaimed by any zone.
zone = NULL;
size = 0;
out:
if (returned_size) {
*returned_size = size;
}
OSAtomicDecrement32Barrier(pFRZCounter); // our thread is leaving FRZ
return zone;
}
由于該方法涉及到虛擬內(nèi)存分配的流程,過(guò)于復(fù)雜,本文就再詳細(xì)展開(kāi)了。理解一點(diǎn)即可,這個(gè)函數(shù)是獲取 系統(tǒng)實(shí)際 分配的內(nèi)存大小,最小16字節(jié)。具體可參考上一節(jié)的alloc流程。
// alloc創(chuàng)建對(duì)象時(shí)最小返回16字節(jié)
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
總結(jié):malloc_size依賴于<malloc/malloc.h>,返回系統(tǒng)分配給對(duì)象的內(nèi)存大小,而且最小是16字節(jié)。下面的第三節(jié)會(huì)對(duì)calloc進(jìn)行詳細(xì)分析為什么對(duì)象以16字節(jié)對(duì)齊。
1.3.sizeOf
sizeof是操作符,不是函數(shù),它的作用對(duì)象是數(shù)據(jù)類型,主要作用于編譯時(shí)。因此,它作用于變量時(shí),也是對(duì)其類型進(jìn)行操作。得到的結(jié)果是該數(shù)據(jù)類型占用空間大小,即size_t類型。
struct test
{
int a; //4 bit
char b; //1 bit
}t1;
NSLog(@"sizeof = %lu ",sizeof(t1));
- 在64位架構(gòu)下,sizeof(int)得到的是4個(gè)字節(jié);
- sizeof(t1),得到的是8個(gè)字節(jié)
問(wèn):int 是4字節(jié),char 是1字節(jié),那么sizeof(t1)的內(nèi)存不是5字節(jié)嗎?
答:這里需要考慮內(nèi)存對(duì)齊的問(wèn)題。關(guān)于內(nèi)存對(duì)齊的問(wèn)題會(huì)在后面講解。
那么對(duì)象的sizeof呢?
NSLog(@"sizeof = %zd", sizeof([NSObject class]));
結(jié)果是 sizeof = 8
問(wèn):為什么是 sizeof= 8 ?
答:因?yàn)樵?4位架構(gòu)下,自定義一個(gè)NSObject對(duì)象,無(wú)論該對(duì)象生命多少個(gè)成員變量,最后得到的內(nèi)存大小都是8個(gè)字節(jié)。
總結(jié):sizeof 只會(huì)計(jì)算類型所占用的內(nèi)存大小,不會(huì)關(guān)心具體的對(duì)象的內(nèi)存布局。NSObject對(duì)象最后只返回內(nèi)存大小為8字節(jié)。
二.內(nèi)存對(duì)齊
在上節(jié)中我們發(fā)現(xiàn)sizeof(test)的結(jié)果是8字節(jié),而不是int 是4字節(jié)+char 是1字節(jié) = 5字節(jié)。這是因?yàn)橄到y(tǒng)進(jìn)行了內(nèi)存對(duì)齊的優(yōu)化處理,接下來(lái)我們帶著這個(gè)疑問(wèn)了解一下什么是內(nèi)存對(duì)齊。
1.內(nèi)存對(duì)齊是什么?
在iOS開(kāi)發(fā)過(guò)程中,編譯器會(huì)自動(dòng)的進(jìn)行字節(jié)對(duì)齊的處理,并且在64位架構(gòu)下,是以8字節(jié)進(jìn)行內(nèi)存對(duì)齊的(另32位是以4字節(jié)對(duì)齊)。
2.內(nèi)存對(duì)齊的原則
對(duì)象的屬性要內(nèi)存對(duì)齊,而對(duì)象本身也需要進(jìn)行內(nèi)存對(duì)齊
- 1.數(shù)據(jù)成員對(duì)齊原則: 結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第
一個(gè)數(shù)據(jù)成員放在offset為0的地方,以后每個(gè)數(shù)據(jù)成員存儲(chǔ)的起始位置要從該成員大小或者成員的子成員大小 - 2.結(jié)構(gòu)體作為成員:如果一個(gè)結(jié)構(gòu)里有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開(kāi)始存儲(chǔ)
- 3.收尾工作:結(jié)構(gòu)體的總大小,也就是sizeof的結(jié)果,必須是其內(nèi)部最大
成員的整數(shù)倍,不足的要補(bǔ)?
3.舉例說(shuō)明
說(shuō)了這么多,看不懂,直接看例子吧!
- 例3.1:結(jié)構(gòu)體 的內(nèi)存對(duì)齊大小計(jì)算
struct struct1 {
char a;
double b;
int c;
short d;
} str1;
struct struct2 {
double b;
char a;
int c;
short d;
} str2;
struct struct3 {
double b;
int c;
char a;
short d;
} str3;
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"%lu——%lu——%lu", sizeof(str1), sizeof(str2), sizeof(str3));
}
return 0;
}
結(jié)果:24——24——16
已知(64位)double為8字節(jié),int為4字節(jié),short為2字節(jié),char為1字節(jié)
內(nèi)存對(duì)齊原則其實(shí)可以簡(jiǎn)單理解為min(m,n)——m為當(dāng)前開(kāi)始的位置,n為所占位數(shù)。當(dāng)m是n的整數(shù)倍時(shí),條件滿足;否則m位空余,m+1,繼續(xù)min算法。
以str1為例
1.先求出所有成員變量的偏移量大小,算出總和
- a,長(zhǎng)度為1,首地址所以它在第0位坐下了,占據(jù)1個(gè)格子。[目前長(zhǎng)度為1]
- b,長(zhǎng)度為8,一開(kāi)始為min(1,8),1不是8的整數(shù)倍,不滿足條件直至min(8,8),所以它在第8位坐下了,占據(jù)8個(gè)格子(即8-15)。[目前長(zhǎng)度為16]。
- c ,長(zhǎng)度為4,一開(kāi)始為min(16,4),16是4的倍數(shù),所以它在第16位坐下了,占據(jù)4格子(即16-19)。目前長(zhǎng)度為20。
- d,長(zhǎng)度為2,一開(kāi)始為min(20,2),滿足條件,在20-21位占據(jù)。[目前長(zhǎng)度22].
2.根據(jù)結(jié)構(gòu)體的總大小為結(jié)構(gòu)體最大基本類型成員變量大小的整數(shù)倍原則,得到str1中最大的變量大小是b的double(8),此時(shí)22不是8的倍數(shù),所以補(bǔ)齊到24.
最終str1的大小為24。
同理str1為24,str3為16。

總結(jié):在結(jié)構(gòu)體中,聲明成員變量的順序不一致,也會(huì)導(dǎo)致最終分配內(nèi)存大小的不同。
- 例3.2:對(duì)象 的內(nèi)存對(duì)齊計(jì)算
- 例3.2.1 對(duì)象單一屬性
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
@interface MyAnimal : NSObject{
int _age;
}
@end
@implementation MyAnimal
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyAnimal *myAnimal = [[MyAnimal alloc]init];
myAnimal.age = 1;
NSLog(@"class_getInstanceSize = %zu",class_getInstanceSize([myAnimal class]));
NSLog(@"malloc_size = %lu",malloc_size(CFBridgingRetain(myAnimal)));
NSLog(@"sizeof = %lu",sizeof(myAnimal));
}
return 0;
}
結(jié)果:
class_getInstanceSize = 16
malloc_size = 16
sizeof = 8
那它的內(nèi)存是如何計(jì)算的呢?那就看一下,OC代碼轉(zhuǎn)換成C++語(yǔ)言,是如何構(gòu)造數(shù)據(jù)的。
在終端執(zhí)行下面的命令,可以將Objective-C對(duì)象轉(zhuǎn)換成C/C++語(yǔ)言:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
在main.m文件所在的目錄下,繼續(xù)執(zhí)行上述講解的Clang的命令。
在main.cpp文件中,我們搜索查找到Animal類的定義,究其精華如下:
struct NSObject_IMPL {
Class isa;//8字節(jié)
};
extern "C" unsigned long OBJC_IVAR_$_MyAnimal$_age;
struct MyAnimal_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age; //4字節(jié)
};
由上可見(jiàn),MyAnimal對(duì)象最終轉(zhuǎn)為結(jié)構(gòu)體MyAnimal_IMPL,所以MyAnimal_IMPL的大小就是MyAnimal的大小。那我們就可參考上面的結(jié)構(gòu)體來(lái)計(jì)算對(duì)象大小,就是類的isa(8字節(jié))+屬性大小的和 = 對(duì)象的實(shí)際大小。但還得參考內(nèi)存對(duì)齊原則(注:arm64是8字節(jié)對(duì)齊)。
問(wèn):那么class_getInstanceSize 應(yīng)該是isa(8) + int (4) = 12,為什么是16呢?
答:從上面的1.1可知class_getInstanceSize獲取的是對(duì)象的實(shí)際屬性總大小,原來(lái)大小確實(shí)是12,但arm64位的內(nèi)存對(duì)齊原則,對(duì)class_getInstanceSize進(jìn)行了8位補(bǔ)齊。12不是8的倍數(shù),只有補(bǔ)齊到16位。
具體內(nèi)存分配如下圖所示:

- 例3.2.2 對(duì)象多屬性
我們?cè)龠M(jìn)一步探討,給MyAnnimal多加2個(gè)屬性,再看一下輸入結(jié)果。
@interface MyAnimal : NSObject@interface MyAnimal : NSObject{
int _age;
int _weight;
int _height;
}
@end
@implementation MyAnimal
@end
結(jié)果:
class_getInstanceSize = 24
malloc_size = 32
sizeof = 8
問(wèn):咦?為什么class_getInstanceSize 和 malloc_size 不相等?
答:我們繼續(xù)執(zhí)行一下clang,得到實(shí)際大小 8+4+4+4 =20,因內(nèi)存對(duì)齊,所以class_getInstanceSize = 24;
但malloc_size是系統(tǒng)分配的大小,以16位對(duì)齊,24不是16的倍數(shù),32才是16的倍數(shù),所以malloc_size = 32。
struct MyAnimal_IMPL {
struct NSObject_IMPL NSObject_IVARS; //8
int _age; // 4
int _weight;// 4
int _height;// 4
};
- 例3.2.3 對(duì)象屬性順序的影響1_大括號(hào)中聲明屬性
//第一種情況,不同類型隨意放
@interface MyAnimal : NSObject{
//isa ; // 8
int _age;//4
NSString *_name; //8
int _height;//4
NSString *_nick;//8
int _weight;//4
}
//第一種情況
class_getInstanceSize = 48
malloc_size = 48
sizeof = 8
再看一種情況,把相同類型的屬性放一起
//第二種情況,把相同類型放一起
@interface MyAnimal : NSObject{
//isa ; // 8
NSString *_name;// 8
NSString *_nick;// 8
int _age;//4
int _height;//4
int _weight;//4
}
//第二種情況
class_getInstanceSize = 40
malloc_size = 48
sizeof = 8
問(wèn):為什么都是同一個(gè)對(duì)象,同樣的屬性,最后計(jì)算出來(lái)的實(shí)際大小卻是不同?
答:還是和內(nèi)存對(duì)齊有關(guān)。
- 第1種情況:isa(8),后面的
age(4),緊跟著name(8),age要補(bǔ)齊到8位成為age(8),同理weight(4)也是補(bǔ)齊到weight(8),則全部大小=isa(8) + name(8) + age(8) + nick(8) + weight(8) + height(4) = 44,因?qū)R8位對(duì)齊,則補(bǔ)充到48.
image.png
第2種情況:isa(8),后面緊跟著name(8),nick(8),weight(4)和age(4)共用8字節(jié),則全部大小=isa(8) + name(8) + nick(8) + age(4)+ height(4) + weight(4) = 40,因符合8位對(duì)齊,則結(jié)果是40.
image.png
總結(jié):
1.對(duì)象的class_getInstanceSize實(shí)際大小為isa(8)+屬性的大小總和,其中各屬性進(jìn)行8位對(duì)齊;
2.對(duì)象的系統(tǒng)分配大小malloc_size根據(jù)以上的實(shí)際大小進(jìn)行16位對(duì)齊;
3.屬性的順序會(huì)影響實(shí)際內(nèi)存的大小,相同類型的最好放在一起.
- 例3.2.4 對(duì)象屬性順序的影響2_直接用@property聲明屬性
//第一種情況,不同類型隨意放
@interface MyAnimal : NSObject
//isa ; // 8
@property(nonatomic,assign)int age;
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)int weight;
@property(nonatomic,copy)NSString *nick;
@property(nonatomic,assign)int height;
@end
//第一種情況
class_getInstanceSize = 40
malloc_size = 48
sizeof = 8
再看一種情況,把相同類型的屬性放一起
//第二種情況,把相同類型放一起
@interface MyAnimal : NSObject
//isa ; // 8
@property(nonatomic,assign)int age;
@property(nonatomic,assign)int height;
@property(nonatomic,assign)int weight;
@property(nonatomic,copy)NSString *name;
@property(nonatomic,copy)NSString *nick;
@end
//第二種情況
class_getInstanceSize = 40
malloc_size = 48
sizeof = 8
問(wèn):真奇怪?直接用@property聲明屬性,屬性的位置不同,但這兩種情況的結(jié)果居然class_getInstanceSize都是40。為什么呢?
答:我們繼續(xù)clang看一下cpp代碼,兩種情況的代碼都如下,系統(tǒng)對(duì)其屬性的位置進(jìn)行了優(yōu)化,把相同類型的放在一起,目的是為了節(jié)省內(nèi)存。
image.png
struct MyAnimal_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
int _weight;
int _height;
NSString *_name;
NSString *_nick;
};
總結(jié):直接用@property聲明屬性,順序不會(huì)對(duì)內(nèi)存的大小產(chǎn)生影響,系統(tǒng)會(huì)自動(dòng)對(duì)其進(jìn)行 優(yōu)化,把相同類型的放在一起。
三.calloc流程分析
在上一章中我們講到了obj = (id)calloc(1, size),我們當(dāng)時(shí)沒(méi)有詳細(xì)進(jìn)入研究。但是objc源碼我們無(wú)從下手,現(xiàn)在我們可以通過(guò)libmalloc源碼來(lái)一探究竟。
3.1.calloc
在libmalloc源碼中新建target,按照objc源碼中的方式調(diào)用,我們把之前算出來(lái)的對(duì)象的屬性大小總和40傳進(jìn)來(lái),然后看一下calloc(1, 40)結(jié)果。
#import <Foundation/Foundation.h>
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
void *p = calloc(1, 40);
NSLog(@"malloc_size = %lu",malloc_size(p));
}
return 0;
}
結(jié)果:malloc_size = 48
問(wèn):為什么我們傳進(jìn)來(lái)的是40,經(jīng)過(guò)clloc之后,系統(tǒng)分配的大小卻是48?
帶著這個(gè)疑問(wèn),我們繼續(xù)往下看calloc里面的具體實(shí)現(xiàn),調(diào)用malloc_zone_calloc
void *
calloc(size_t num_items, size_t size)
{
void *retval;
retval = malloc_zone_calloc(default_zone, num_items, size);
if (retval == NULL) {
errno = ENOMEM;
}
return retval;
}
3.2. malloc_zone_calloc
我們?cè)偻驴搓P(guān)鍵代碼ptr = zone->calloc(zone, num_items, size);
void *
malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);
void *ptr;
if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) {
internal_check();
}
ptr = zone->calloc(zone, num_items, size);
if (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);
return ptr;
}
但是我們進(jìn)入zone->calloc后發(fā)現(xiàn)都是看不懂的東西,zone是malloc_zone_t類型,其中有個(gè)方法(calloc))(struct _malloc_zone_t zone, size_t num_items, size_t size);,沒(méi)法往下進(jìn)入了。那我們?cè)傧胍幌缕渌椒ā?/p>
typedef struct _malloc_zone_t {
/* Only zone implementors should depend on the layout of this structure;
Regular callers should use the access functions below */
void *reserved1; /* RESERVED FOR CFAllocator DO NOT USE */
void *reserved2; /* RESERVED FOR CFAllocator DO NOT USE */
size_t (* MALLOC_ZONE_FN_PTR(size))(struct _malloc_zone_t *zone, const void *ptr); /* returns the size of a block or 0 if not in this zone; must be fast, especially for negative answers */
void *(* MALLOC_ZONE_FN_PTR(malloc))(struct _malloc_zone_t *zone, size_t size);
void *(* MALLOC_ZONE_FN_PTR(calloc))(struct _malloc_zone_t *zone, size_t num_items, size_t size); /* same as malloc, but block returned is set to zero */
void *(* MALLOC_ZONE_FN_PTR(valloc))(struct _malloc_zone_t *zone, size_t size); /* same as malloc, but block returned is set to zero and is guaranteed to be page aligned */
void (* MALLOC_ZONE_FN_PTR(free))(struct _malloc_zone_t *zone, void *ptr);
void *(* MALLOC_ZONE_FN_PTR(realloc))(struct _malloc_zone_t *zone, void *ptr, size_t size);
void (* MALLOC_ZONE_FN_PTR(destroy))(struct _malloc_zone_t *zone); /* zone is destroyed and all memory reclaimed */
const char *zone_name;
/* Optional batch callbacks; these may be NULL */
unsigned (* MALLOC_ZONE_FN_PTR(batch_malloc))(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested); /* given a size, returns pointers capable of holding that size; returns the number of pointers allocated (maybe 0 or less than num_requested) */
void (* MALLOC_ZONE_FN_PTR(batch_free))(struct _malloc_zone_t *zone, void **to_be_freed, unsigned num_to_be_freed); /* frees all the pointers in to_be_freed; note that to_be_freed may be overwritten during the process */
struct malloc_introspection_t * MALLOC_INTROSPECT_TBL_PTR(introspect);
unsigned version;
/* aligned memory allocation. The callback may be NULL. Present in version >= 5. */
void *(* MALLOC_ZONE_FN_PTR(memalign))(struct _malloc_zone_t *zone, size_t alignment, size_t size);
/* free a pointer known to be in zone and known to have the given size. The callback may be NULL. Present in version >= 6.*/
void (* MALLOC_ZONE_FN_PTR(free_definite_size))(struct _malloc_zone_t *zone, void *ptr, size_t size);
/* Empty out caches in the face of memory pressure. The callback may be NULL. Present in version >= 8. */
size_t (* MALLOC_ZONE_FN_PTR(pressure_relief))(struct _malloc_zone_t *zone, size_t goal);
/*
* Checks whether an address might belong to the zone. May be NULL. Present in version >= 10.
* False positives are allowed (e.g. the pointer was freed, or it's in zone space that has
* not yet been allocated. False negatives are not allowed.
*/
boolean_t (* MALLOC_ZONE_FN_PTR(claimed_address))(struct _malloc_zone_t *zone, void *ptr);
} malloc_zone_t;
打斷點(diǎn)試一下,我們看到其實(shí)調(diào)用的是default_zone_calloc,是不是有一種山窮水復(fù)疑無(wú)路,柳暗花明又一村的感覺(jué)。

3.3. 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);
}
按上面的思路,我們給zone = runtime_default_zone()打斷點(diǎn),看一下效果。發(fā)現(xiàn)是調(diào)用 naco_calloc
- image.png
3.4. naco_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;
}
if (total_bytes <= NANO_MAX_SIZE) { // NANO_MAX_SIZE 256
void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1);
if (p) {
return p;
} else {
/* FALLTHROUGH to helper zone */
}
}
malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
return zone->calloc(zone, 1, total_bytes);
}
查看NANO_MAX_SIZE 發(fā)現(xiàn)為 256 ,此時(shí)大小小于256,所以從上面斷點(diǎn)往下執(zhí)行,走到*void p = _nano_malloc_check_clear(nanozone, total_bytes, 1);
3.5. _nano_malloc_check_clear
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);
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));
if (ptr) {
/**因?yàn)椴蛔哌@里,此處省略三千字*/
} else {
ptr = segregated_next_block(nanozone, pMeta, slot_bytes, mag_index);
}
if (cleared_requested && ptr) {
memset(ptr, 0, slot_bytes); // TODO: Needs a memory barrier after memset to ensure zeroes land first?
}
return ptr;
}
我們跟著斷點(diǎn)往下走,代碼執(zhí)行到了segregated_size_to_fit(nanozone, size, &slot_key)
3.6. segregated_size_to_fit
這個(gè)代碼是不是有一種似曾相識(shí)的感覺(jué),先右移幾位,再左移幾位。沒(méi)錯(cuò),就是我們前面講的對(duì)象的屬性8位對(duì)齊。此處的NANO_REGIME_QUANTA_SIZE為16,此處我們可以理解為16位對(duì)齊。
#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; // 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;
}
再看一下我們?cè)瓉?lái)傳進(jìn)來(lái)的size 是 40,在經(jīng)過(guò) (40 + 16 - 1) >> 4 << 4 操作后,結(jié)果為48,也就是16的整數(shù)倍——即16字節(jié)對(duì)齊。
那么我們3.1中提出的問(wèn)題為什么傳進(jìn)來(lái)的是40,系統(tǒng)分配結(jié)果卻是48的答案就找到了,因?yàn)?6字節(jié)對(duì)齊。
總結(jié):calloc進(jìn)行對(duì)象系統(tǒng)分配內(nèi)存時(shí)使用的是16字節(jié)對(duì)齊,為的是防止內(nèi)存溢出。
3.7 calloc流程圖

參考鏈接:
關(guān)于NSObject對(duì)象的內(nèi)存布局
iOS探索內(nèi)存對(duì)齊&malloc源碼
iOS基礎(chǔ)知識(shí)之@property 和 Ivar 的區(qū)別



