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

前話:

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

IMG_1983.JPG

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

image.png

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

image.png

    • 例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é)。

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 ,此時(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流程圖

image.png

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容