OC中的获取内存大小方式及比较

获取内存大小

通常获取内存大小有3种方式:

  • sizeof
  • class_getInstanceSize
  • malloc_size

示例

我们增加如下测试代码

#import 
#import 
@interface LYPerson : NSObject
@property(nonatomic, strong) NSString *name;
@end
@implementation LYPerson

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LYPerson *p1 = [LYPerson alloc];
        NSLog(@"%ld",sizeof(p1)); // 1
        NSLog(@"%ld",class_getInstanceSize([p1 class])); //2
        NSLog(@"%ld",malloc_size((__bridge const void*)(p1))); // 3
    }
    return 0;
}

我们看下输出结果

2020-09-09 22:30:59.588645+0800 KCObjc[37314:1234370] 8
2020-09-09 22:30:59.589573+0800 KCObjc[37314:1234370] 16
2020-09-09 22:30:59.589797+0800 KCObjc[37314:1234370] 16

从打印结果我们可以直观的看到,打印结果为 8,16,16。

分析

sizeof :

接下来,我们分析该代码,当我们想查看sizeof源码实现时,但无法进入。但我们可以使用汇编简单看下该过程,为方便查看,我们将代码简化成如下形式:

int a = sizeof(p1);
int b = class_getInstanceSize([p1 class]);
int c = malloc_size((__bridge const void*)(p1));

在 最后一行设置一个断点,并查看汇编Debug->Debug WorkFlow -> Always show Dissembly ,结果如下所示:

 //3   0x100000e6f <+47>:  movl   $0x8, -0x1c(%rbp)
    0x100000e76 <+54>:  movq   -0x10(%rbp), %rdi
    0x100000e7a <+58>:  callq  0x100000ed0  ; symbol stub for: objc_opt_class
    0x100000e7f <+63>:  movq   %rax, %rdi
// 1  0x100000e82 <+66>:  callq  0x100000eb2 ; symbol stub for: class_getInstanceSize
    0x100000e87 <+71>:  movl   %eax, -0x18(%rbp)
->  0x100000e8a <+74>:  movq   -0x10(%rbp), %rdi
// 2    0x100000e8e <+78>:  callq  0x100000eb8  ; symbol stub for: malloc_size

从上面汇编代码可知:
1,malloc_size:是一个函数调用。
2,class_getInstanceSize:也是一个函数调用。
3,sizeof:并没有使用 call指令,从而判断出它不是一个函数p1LYPerson *类型,则 sizeof(p1)计算的是 LYPerson *指针类型的大小, 他直接将结果 立即数0x8传到了 bp - 0x1c 内存处。

class_getInstanceSize:

我们通过objc4的源码进行分析,其方法实现为:

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()); // 2,
  }

  uint32_t unalignedInstanceSize() const {
        ASSERT(isRealized());
        return data()->ro()->instanceSize; // 1
   }

  static inline uint32_t word_align(uint32_t x) {
      return (x + WORD_MASK) & ~WORD_MASK; // 3
  }
  • 1,返回未对齐之前的对象大小,这里返回16。
  • 2,返回对齐之后的对象大小。
  • 3,内存对齐算法,在这里 x = 16, WORD_MASK = 7UL,类的实例对象实际占用的内存为 8 的倍数,可以简单理解为,实例对象实际占用的空间为 8 字节对齐 ,对齐算法如下:
     0001 0111 // 
->   1111 1000 // 
&    0001 0000 // 与操作的最终结果
  • 1,23 = x + WORD_MASK = 16 + 7,23的二进制为0001 0111
  • 2,WORD_MASK 取反结果为 1111 1000
  • 3,与操作后的最终结果 0001 0000为 16
calloc

malloc_size实际上是用来获取变量在内存中分配的空间大小,我们通过研究calloc,来反推malloc_size。 在OC对象的alloc过程一文中我们讲到OC对象是通过calloc函数来开辟空间的。接下来我们通过calloc来研究OC是怎样开辟内存的,我们增加以下调试代码

void *p = calloc(1, 40);

通过断点我们进入 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;
}

然后进入到 malloc_zone_calloc函数

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); // 1
    
    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;
}
  • 1,在该处这里又调用了 zone -> calloc 方法。

当我们进入zone -> calloc 函数时发现,直接跳到了 void *(* MALLOC_ZONE_FN_PTR(calloc))(struct _malloc_zone_t *zone, size_t num_items, size_t这个地方,这样我们就无法进行下一步调试了。
回到 ptr = zone->calloc(zone, num_items, size) 行,这里我们可以通过打印 zone->calloc来了解该函数类型

(lldb) po zone->calloc
(.dylib`default_zone_calloc at malloc.c:331)

从这里我们可以看出 zone->callocmalloc.c文件中 第331行处的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->calloc

(lldb) po zone->calloc
(.dylib`nano_calloc at nano_malloc.c:878)

我们将断点设置到该位置处:

static void *
nano_malloc(nanozone_t *nanozone, size_t size)
{
    if (size <= NANO_MAX_SIZE) {
        void *p = _nano_malloc_check_clear(nanozone, size, 0);
        if (p) {
            return p;
        } else {
            /* FALLTHROUGH to helper zone */
        }
    }

    malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
    return zone->malloc(zone, size);
}

这样的话,我们就来到了

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;
      // Note slot_key is set here
    size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key); 
    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函数返回的是对象分配的内存空间大小

#define SHIFT_NANO_QUANTUM      4
#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; // 1  // Historical behavior 
    }
    k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM;// 2   // round up and shift for number of quanta 
    slot_bytes = k << SHIFT_NANO_QUANTUM;   // 3                        // multiply by power of two quanta size
    *pKey = k - 1;                                                  // Zero-based!

    return slot_bytes;
}
  • 1,当 size == 0,返回 NANO_REGIME_QUANTA_SIZENANO_REGIME_QUANTA_SIZE = 1<< 4 = 0x1000 = 16
  • 2,3 : 进行16字节对齐,为方便解释,我们 假设 size = 24, 24 + 16 - 1 = 39,然后右移4位39 = 0x00100111 ,右移4位 等于 0x00000010,最后,左移4位 ,得到结果 0x00100000 = 32

通过对 calloc方法进行分析,我们可以看出,OC对象在实际分配空间时是16字节对齐。通过malloc_size获取的值,为calloc开辟的实际空间值。

总结:

1,sizeof:不是一个函数,而是一个编译器特性,在编译时期,就将类型的值计算出来,它表示的是一种数据类型的大小,可以是 指针基本数据类型等等。
2,class_getInstanceSize:获取的是对象实际占用的内存空间大小。OC对象实际占用的空间是8字节对齐
3,calloc:是对OC对象开辟的内存空间,malloc_size,是系统给对象分配的空间,calloc在开辟空间时,采用的是16字节对齐的方式。

你可能感兴趣的:(OC中的获取内存大小方式及比较)