iOS之2內(nèi)存對齊&calloc分析

前話:

在了解內(nèi)存對齊之前先了解一下各數(shù)據(jù)類型在內(nèi)存中的大小涨薪,目前我們比較常用的是64位系統(tǒng)劫笙,所以我們的研究對象統(tǒng)一采用64位的大小作為參考。

IMG_1983.JPG

一. 如何獲取內(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。

image.png

總結(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)存分配如下圖所示:

image.png

    • 例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.
    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位對齊隧熙,則結(jié)果是40.


    image.png

總結(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)存端壳。


image.png
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ù)疑無路,柳暗花明又一村的感覺粤策。

image.png

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 秩贰,此時大小小于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流程圖

image.png

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末虫碉,一起剝皮案震驚了整個濱河市贾惦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌敦捧,老刑警劉巖须板,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異兢卵,居然都是意外死亡习瑰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門秽荤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甜奄,“玉大人柠横,你說我怎么就攤上這事】涡郑” “怎么了牍氛?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長烟阐。 經(jīng)常有香客問我糜俗,道長,這世上最難降的妖魔是什么曲饱? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任悠抹,我火速辦了婚禮,結(jié)果婚禮上扩淀,老公的妹妹穿的比我還像新娘楔敌。我一直安慰自己,他們只是感情好驻谆,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布卵凑。 她就那樣靜靜地躺著,像睡著了一般胜臊。 火紅的嫁衣襯著肌膚如雪勺卢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天象对,我揣著相機(jī)與錄音黑忱,去河邊找鬼。 笑死勒魔,一個胖子當(dāng)著我的面吹牛甫煞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播冠绢,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼抚吠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了弟胀?” 一聲冷哼從身側(cè)響起楷力,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎孵户,沒想到半個月后萧朝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡延届,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年剪勿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片方庭。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡厕吉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出械念,到底是詐尸還是另有隱情头朱,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布龄减,位于F島的核電站项钮,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏希停。R本人自食惡果不足惜烁巫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宠能。 院中可真熱鬧亚隙,春花似錦、人聲如沸违崇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽羞延。三九已至渣淳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間伴箩,已是汗流浹背入愧。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嗤谚,地道東北人砂客。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像呵恢,于是被迫代替她去往敵國和親鞠值。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345