iOS疑问之 alloc内存申请,NSObject不调用alloc

794 阅读4分钟

我们已经知道了alloc中会申请内存空间。如何申请。具体申请多少。如何计算呢? 首先我们应该知道不同类型所占空间

COC32位64位
boolBOOL11
singed charint8_t,BOOL(32位)11
unsigned charBoolean11
shortint 16_t11
unsigned shortunchar22
int int32_tNSinterger/boolean_t()3244
unsigned intNSUintetger44
lognnsinterger(64)48
unsigned lognNSUintetger(64)48
long lognint64_t88
floatCGFloat(32)44
doubleCGFloat(64)88

根据内存对齐原则我们可以进行计算。 2647E072-D8EF-4C94-B808-DEF0FF26F02C.png 计算内存的三个方法sizeofclass_getInstanceSizemalloc_size

sizeof是一个运算符,传进来类型,会计算出这个类型占多大内存,在编译器编一阶段就已经确定大小。不能动态分配内存空间大小

class_getInstanceSize用来获取类的实例对象所占内存大小,返回具体字节数,本质获取对象中成员变量的内存大小。依赖于/#import <objc/runtime.h>

malloc_size计算对象时间分配的内存大小,系统完成(16字节对齐)。 依赖于#import <malloc/malloc.h>

sizeof(person) 返回的是person的指针。所以是8字节

class_getInstanceSize([LGPerson class])的内存取决与LGPerson的成员变量

image.png

根据8字节对齐原理总共32字节。验证结果

image.png

如果没有成员变量呢。删除所以成员变量

image.png

结果

image.png

类的组成经过我们探索发现。类的实例变量最小也会有8字节因为isa

class_getInstanceSize的实现逻辑我们可以通过 objc4-818.2 找到便于你有问题可以查看文章,定位到代码

size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}
uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }
   // May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() const {
        ASSERT(isRealized());
        return data()->ro()->instanceSize;
    }

data()->ro()->instanceSize 的大小在编译的时候以及确定了,默认是8 的倍数。有可能不是8 的倍数。就需要word_align(unalignedInstanceSize())来保证

WORD_MASK = 7
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

8字节对齐算法假设x为8

8 + 7 & ~7

0000 1111 & 1111 1000 = 0000 1000 = 8

class_getInstanceSize的底层实现可以得到。成员变量8字节对齐的结果加8字节的isa

通过lldb来查看内存数据

  • x/nfu 有规律打印,iOS为小端,打印结果与X 刚好相反
  • n 要显示的内存单元个数
  • u 表示一个地址单元的长度
  • b 表示单字节
  • h 表示双字节
  • w 表示四字节
  • ===========
  • f 表示显示方式可取值如下:
  • x 十六进制显示
  • d 十进制显示
  • u 十进制显示无符号整型
  • o 八进制显示
  • t 二进制显示
  • a 十六进制显示
  • i 指令地址格式
  • c 字符格式显示
  • f 浮点数

image.png 我们用lldb来查看一下

image.png ”1“的区域为对象首地址。后面为成员变量的值

image.png

”2“存的是isa。需要与ISA_MASK & 运算才正常打印

image.png

image.png

类方法和对象方法会不会影响呢? image.png image.png 并不会影响。

malloc_size 返回16的倍数

image.png

16 + 16 + 16 = 48字节malloc_size返回48字节 具体是如何开辟内存的 通过libmalloc 源码来探索,我们知道alloc核心是calloc,所以我们测试

image.png

void *
calloc(size_t num_items, size_t size) 
{
return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);
}
----_malloc_zone_calloc-----
MALLOC_NOINLINE
static void *
_malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size,
malloc_zone_options_t mzo)
{
MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);
void *ptr;
if (malloc_check_start) {
internal_check();
}
ptr = zone->calloc(zone, num_items, size);
if (os_unlikely(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);
if (os_unlikely(ptr == NULL)) {
malloc_set_errno_fast(mzo, ENOMEM);
}
return ptr;
}

通过源码分析。ptr应该就是核心了,ptr的实现是。但是无法直接查看到源码。

image.png

image.png

image.png

image.png 最后定位到

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) {
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_malloc_check_clear

image.png segregated_size_to_fit计算内存大小

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;
}

NANO_REGIME_QUANTA_SIZE宏为16,SHIFT_NANO_QUANTUM为4、算法解读size + 16 - 1 右移4位,再左移4位,size = 40。40+15=55。

0011 0111 右移 0000 0011 左移 0011 0000 转为10进制48。右移4位,左移四位,16进制对齐。

segregated_next_block是开辟内存的空间 堆区开辟空间是不连续的,期间可能因多线程,小于最大地址限制,需要while。直至开辟空间成功,返回指针地址

[NSObject alloc]没用执行 alloc而是 objc_alloc。需要通过LLVM来探索。 objc_alloc找到调用的地方

image.png

tryGenerateSpecializedMessageSend负责发生消息,sel 相当于 alloc进行特殊消息处理。

return CGF.EmitObjCAllocWithZone(Receiver,CGF.ConvertType(ResultType));查看此方法

/// Allocate the given objc object.
///   call i8* \@objc_alloc(i8* %value)
llvm::Value *CodeGenFunction::EmitObjCAlloc(llvm::Value *value,
                                            llvm::Type *resultType) {
  return emitObjCValueOperation(*this, value, resultType,
                                CGM.getObjCEntrypoints().objc_alloc,
                                "objc_alloc");
}

由此可知,alloc 会去调用obcj_alloc方法。tryGenerateSpecializedMessageSend这个方法哪里调用,

image.png 此代码意思是。runtime在进行消息发生会调用这个方法,判断是不是特殊消息发生,如果是特殊消息发送,

NSObject不走alloc,直接objc_alloc。因为是系统级别的,所以在初始化就会执行。LLVM对于代码的优化