前話:
在了解內(nèi)存對齊之前先了解一下各數(shù)據(jù)類型在內(nèi)存中的大小涨薪,目前我們比較常用的是64位系統(tǒng)劫笙,所以我們的研究對象統(tǒng)一采用64位的大小作為參考。
一. 如何獲取內(nèi)存的大小
獲取NSObject對象的內(nèi)存大小卖陵,需要用到以下幾個函數(shù):
- 1.class_getInstanceSize
- 2.malloc_size
- 3.sizeOf
我們先來一段代碼宋雏,然后調(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;
}
控制臺打印如下:
class_getInstanceSize = 8
malloc_size = 16
sizeOf = 8
結(jié)果是 大小都不一樣恍风,為什么呢蹦狂?我們帶著疑問來對上面幾個函數(shù)進(jìn)行分析一下吧。
1.1 class_getInstanceSize
我們先通過源碼來看一下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é)對齊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)建一個實(shí)例對象所需內(nèi)存大小。就是獲取對象的全部屬性的大小總和廷没,然后按8位對齊獲得糊饱,不足8位補(bǔ)齊8位。
?疑問點(diǎn):目前例子中的對象實(shí)例化颠黎,但是沒有屬性另锋,但是為什么還是占位8字節(jié)。
答:該8字節(jié)是對象的實(shí)例化后isa的大小8字節(jié)狭归。(后續(xù)再詳細(xì)探討)
1.2.malloc_size
這個函數(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)存分配的流程室梅,過于復(fù)雜,本文就再詳細(xì)展開了潭流。理解一點(diǎn)即可竞惋,這個函數(shù)是獲取 系統(tǒng)實(shí)際 分配的內(nèi)存大小柜去,最小16字節(jié)灰嫉。具體可參考上一節(jié)的alloc流程。
// alloc創(chuàng)建對象時最小返回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)分配給對象的內(nèi)存大小讼撒,而且最小是16字節(jié)。下面的第三節(jié)會對calloc進(jìn)行詳細(xì)分析為什么對象以16字節(jié)對齊股耽。
1.3.sizeOf
sizeof是操作符根盒,不是函數(shù),它的作用對象是數(shù)據(jù)類型物蝙,主要作用于編譯時炎滞。因此,它作用于變量時诬乞,也是對其類型進(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個字節(jié);
- sizeof(t1)票堵,得到的是8個字節(jié)
問:int 是4字節(jié)扼睬,char 是1字節(jié),那么sizeof(t1)的內(nèi)存不是5字節(jié)嗎?
答:這里需要考慮內(nèi)存對齊的問題悴势。關(guān)于內(nèi)存對齊的問題會在后面講解窗宇。
那么對象的sizeof呢措伐?
NSLog(@"sizeof = %zd", sizeof([NSObject class]));
結(jié)果是 sizeof = 8
問:為什么是 sizeof= 8 ?
答:因?yàn)樵?4位架構(gòu)下担映,自定義一個NSObject對象废士,無論該對象生命多少個成員變量,最后得到的內(nèi)存大小都是8個字節(jié)蝇完。
總結(jié):sizeof 只會計(jì)算類型所占用的內(nèi)存大小官硝,不會關(guān)心具體的對象的內(nèi)存布局。NSObject對象最后只返回內(nèi)存大小為8字節(jié)短蜕。
二.內(nèi)存對齊
在上節(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)存對齊的優(yōu)化處理朋魔,接下來我們帶著這個疑問了解一下什么是內(nèi)存對齊岖研。
1.內(nèi)存對齊是什么?
在iOS開發(fā)過程中警检,編譯器會自動的進(jìn)行字節(jié)對齊的處理孙援,并且在64位架構(gòu)下,是以8字節(jié)進(jìn)行內(nèi)存對齊的(另32位是以4字節(jié)對齊)扇雕。
2.內(nèi)存對齊的原則
對象的屬性要內(nèi)存對齊拓售,而對象本身也需要進(jìn)行內(nèi)存對齊
- 1.數(shù)據(jù)成員對齊原則: 結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第
一個數(shù)據(jù)成員放在offset為0的地方镶奉,以后每個數(shù)據(jù)成員存儲的起始位置要從該成員大小或者成員的子成員大小 - 2.結(jié)構(gòu)體作為成員:如果一個結(jié)構(gòu)里有某些結(jié)構(gòu)體成員础淤,則結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開始存儲
- 3.收尾工作:結(jié)構(gòu)體的總大小,也就是sizeof的結(jié)果哨苛,必須是其內(nèi)部最大
成員的整數(shù)倍鸽凶,不足的要補(bǔ)?
3.舉例說明
說了這么多,看不懂建峭,直接看例子吧!
- 例3.1:結(jié)構(gòu)體 的內(nèi)存對齊大小計(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)存對齊原則其實(shí)可以簡單理解為min(m,n)——m為當(dāng)前開始的位置亿蒸,n為所占位數(shù)凑兰。當(dāng)m是n的整數(shù)倍時,條件滿足祝懂;否則m位空余票摇,m+1,繼續(xù)min算法砚蓬。
以str1為例
1.先求出所有成員變量的偏移量大小矢门,算出總和
- a,長度為1,首地址所以它在第0位坐下了祟剔,占據(jù)1個格子隔躲。[目前長度為1]
- b,長度為8物延,一開始為min(1,8)宣旱,1不是8的整數(shù)倍,不滿足條件直至min(8,8)叛薯,所以它在第8位坐下了浑吟,占據(jù)8個格子(即8-15)。[目前長度為16]耗溜。
- c ,長度為4组力,一開始為min(16,4),16是4的倍數(shù)抖拴,所以它在第16位坐下了燎字,占據(jù)4格子(即16-19)。目前長度為20阿宅。
- d候衍,長度為2,一開始為min(20,2),滿足條件洒放,在20-21位占據(jù)蛉鹿。[目前長度22].
2.根據(jù)結(jié)構(gòu)體的總大小為結(jié)構(gòu)體最大基本類型成員變量大小的整數(shù)倍原則,得到str1中最大的變量大小是b的double(8)拉馋,此時22不是8的倍數(shù)榨为,所以補(bǔ)齊到24.
最終str1的大小為24惨好。
同理str1為24煌茴,str3為16。
總結(jié):在結(jié)構(gòu)體中日川,聲明成員變量的順序不一致蔓腐,也會導(dǎo)致最終分配內(nèi)存大小的不同。
- 例3.2:對象 的內(nèi)存對齊計(jì)算
- 例3.2.1 對象單一屬性
#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++語言,是如何構(gòu)造數(shù)據(jù)的分歇。
在終端執(zhí)行下面的命令傀蓉,可以將Objective-C對象轉(zhuǎn)換成C/C++語言:
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é)
};
由上可見,MyAnimal對象最終轉(zhuǎn)為結(jié)構(gòu)體MyAnimal_IMPL谱净,所以MyAnimal_IMPL的大小就是MyAnimal的大小窑邦。那我們就可參考上面的結(jié)構(gòu)體來計(jì)算對象大小,就是類的isa(8字節(jié))+屬性大小的和 = 對象的實(shí)際大小壕探。但還得參考內(nèi)存對齊原則(注:arm64是8字節(jié)對齊)冈钦。
問:那么class_getInstanceSize 應(yīng)該是isa(8) + int (4) = 12,為什么是16呢?
答:從上面的1.1可知class_getInstanceSize獲取的是對象的實(shí)際屬性總大小李请,原來大小確實(shí)是12瞧筛,但arm64位的內(nèi)存對齊原則,對class_getInstanceSize進(jìn)行了8位補(bǔ)齊导盅。12不是8的倍數(shù)驾窟,只有補(bǔ)齊到16位。
具體內(nèi)存分配如下圖所示:
- 例3.2.2 對象多屬性
我們再進(jìn)一步探討认轨,給MyAnnimal多加2個屬性绅络,再看一下輸入結(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
問:咦嘁字?為什么class_getInstanceSize 和 malloc_size 不相等恩急?
答:我們繼續(xù)執(zhí)行一下clang,得到實(shí)際大小 8+4+4+4 =20纪蜒,因內(nèi)存對齊衷恭,所以class_getInstanceSize = 24;
但malloc_size是系統(tǒng)分配的大小,以16位對齊纯续,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 對象屬性順序的影響1_大括號中聲明屬性
//第一種情況窗看,不同類型隨意放
@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
問:為什么都是同一個對象显沈,同樣的屬性,最后計(jì)算出來的實(shí)際大小卻是不同逢唤?
答:還是和內(nèi)存對齊有關(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位對齊院尔,則補(bǔ)充到48.
第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位對齊隧熙,則結(jié)果是40.
總結(jié):
1.對象的class_getInstanceSize實(shí)際大小為isa(8)+屬性的大小總和片挂,其中各屬性進(jìn)行8位對齊;
2.對象的系統(tǒng)分配大小malloc_size根據(jù)以上的實(shí)際大小進(jìn)行16位對齊;
3.屬性的順序會影響實(shí)際內(nèi)存的大小,相同類型的最好放在一起.
- 例3.2.4 對象屬性順序的影響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
問:真奇怪躏敢?直接用@property聲明屬性闷愤,屬性的位置不同,但這兩種情況的結(jié)果居然class_getInstanceSize都是40件余。為什么呢讥脐?
答:我們繼續(xù)clang看一下cpp代碼,兩種情況的代碼都如下啼器,系統(tǒng)對其屬性的位置進(jìn)行了優(yōu)化旬渠,把相同類型的放在一起,目的是為了節(jié)省內(nèi)存端壳。
struct MyAnimal_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
int _weight;
int _height;
NSString *_name;
NSString *_nick;
};
總結(jié):直接用@property聲明屬性告丢,順序不會對內(nèi)存的大小產(chǎn)生影響,系統(tǒng)會自動對其進(jìn)行 優(yōu)化损谦,把相同類型的放在一起岖免。
三.calloc流程分析
在上一章中我們講到了obj = (id)calloc(1, size),我們當(dāng)時沒有詳細(xì)進(jìn)入研究照捡。但是objc源碼我們無從下手颅湘,現(xiàn)在我們可以通過libmalloc源碼來一探究竟。
3.1.calloc
在libmalloc源碼中新建target麻敌,按照objc源碼中的方式調(diào)用,我們把之前算出來的對象的屬性大小總和40傳進(jìn)來栅炒,然后看一下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
問:為什么我們傳進(jìn)來的是40术羔,經(jīng)過clloc之后,系統(tǒng)分配的大小卻是48乙漓?
帶著這個疑問级历,我們繼續(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
我們再往下看關(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類型叭披,其中有個方法(calloc))(struct _malloc_zone_t zone, size_t num_items, size_t size);寥殖,沒法往下進(jìn)入了玩讳。那我們再想一下其他方法。
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ù)疑無路,柳暗花明又一村的感覺粤策。
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
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 秩贰,此時大小小于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
這個代碼是不是有一種似曾相識的感覺愈魏,先右移幾位觅玻,再左移幾位。沒錯培漏,就是我們前面講的對象的屬性8位對齊串塑。此處的NANO_REGIME_QUANTA_SIZE為16,此處我們可以理解為16位對齊北苟。
#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;
}
再看一下我們原來傳進(jìn)來的size 是 40桩匪,在經(jīng)過 (40 + 16 - 1) >> 4 << 4 操作后,結(jié)果為48友鼻,也就是16的整數(shù)倍——即16字節(jié)對齊傻昙。
那么我們3.1中提出的問題為什么傳進(jìn)來的是40,系統(tǒng)分配結(jié)果卻是48的答案就找到了,因?yàn)?6字節(jié)對齊彩扔。
總結(jié):calloc進(jìn)行對象系統(tǒng)分配內(nèi)存時使用的是16字節(jié)對齊妆档,為的是防止內(nèi)存溢出。
3.7 calloc流程圖
參考鏈接:
關(guān)于NSObject對象的內(nèi)存布局
iOS探索內(nèi)存對齊&malloc源碼
iOS基礎(chǔ)知識之@property 和 Ivar 的區(qū)別