前言
OC作为一门万物皆对象的语言,那么对象的创建、开辟内存空间究竟是怎样做到的?我们平时创建对象用到alloc
alloc
底层到底怎样玩的,我们今天就探索一下alloc
底层流程
一、alloc对象的指针地址和内存地址
我们先来玩个demo看一下
探究分析得出结论
打印得出
p1
、p2
、p3
得到的堆内存地址(对象的指针地址)是一样的都是0x600002a048e0
这3个对象指向同一块内存区域 得到的栈内存地址(对象的指针指向的内存地址)是不同的 内存连续开辟 每个相隔8字节alloc居右开辟内存的功能,而init没有开辟内存的功能
p4
和p1
、p2
、p3
打印的对象的指针地址和对象的指针指向的内存地址是不一样的堆的内存地址是由低地址 到 高地址 如上图
p1
、p2
、p3
堆内存地址是:0x600002a048e0
而p4
的堆内存地址是:0x600002a048f0
栈的内存地址是由高地址 到 低地址 如上图
p1
、p2
、p3
栈内存地址是:0x7ffee5206078
、0x7ffee5206070
、0x7ffee5206068
而p4
的栈内存地址是:0x7ffee5206060
二、底层探索的三种方法
准备工作
1.alloc的源码,需要去苹果的SourceBrowser下载最新版本
2.编译问题解决可以参考源码编译调试
1.符号断点
在需要调试的位置打上断点,当进入断点的时候按住control
+ step into
进入下一步 然后看到objc_alloc
,然后加一个系统符号断点 具体流程如下图
2.汇编跟中调试 + 符号断点
在需要调试的位置打上断点,设置Xcode
->Debug
->Debug Workflow
-> Always Show Disassembly
当进入断点的时候按住control
+ step into
进入下一步 然后看到objc_alloc
,然后加一个系统符号断点(添加系统符号断点步骤跟1.符号断点
一样) 具体流程如下图
3.直接下符号断点
想探索哪个方法就直接给这个方法添加符号断点 暴力输出 比如我们探究alloc方法就直接给alloc添加符号断点 如下图
三、汇编结合源码调试分析
上面的三种探索方法我们已经知道了objc_alloc
是属于libobjc
那么我们就把源码编译跑起来 更深入的进去探索一下
探索流程如下断点进入alloc
+ (id)alloc {
return _objc_rootAlloc(self);
}
在进入 _objc_rootAlloc
id _objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
在进入 callAlloc
到这里到底走哪个方法
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
return _objc_rootAllocWithZone(cls, nil);
}
#endif
// No shortcuts available.
if (allocWithZone) {
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
通过汇编结合代码判断 是先走 _objc_rootAllocWithZone
然后再走objc_msgSend
如下图所示
在进入 _objc_rootAllocWithZone
id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
在进入 _class_createInstanceFromZone
static ALWAYS_INLINE id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
`先计算出需要的内存空间大小`
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
`向系统申请开辟内存,返回地址指针 `
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
`关联到相应的类 `
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
1.在这里就有疑问了 如何 计算出需要的内存空间大小
进入 size = cls->instanceSize(extraBytes)
这里探索究竟 进入instanceSize
inline size_t instanceSize(size_t extraBytes) const {
`快速计算内存大小`
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
`计算类中所有成员变量需要的内存大小`
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
`不够16字节 返回16字节`
if (size < 16) size = 16;
return size;
}
在进入 cache.fastInstanceSize
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
if (__builtin_constant_p(extra) && extra == 0) {
return _flags
& FAST_CACHE_ALLOC_MASK16;
} else {
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
在进入 align16
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
我们探究一下 align16
的具体实现以align16(8)
为例子:
(8 + size_t(15)) & ~size_t(15);
(8+15) & ~15
0000 1111
= 15
取反得到下面~15
1111 0000
= ~15
23&~15
0001 0111
= 23
1111 0000
= ~15
结果 0001 0000
= 16
align16
算法就是向上取整数 16的倍数 不足16倍数的全部抹掉
2.向系统申请开辟内存,返回地址指针
进入 obj = (id)calloc(1, size)
这里探索究竟 calloc
首先根据instanceSize
方法计算出需要内存的大小,然后传入size
返回obj
因此obj
是只想内存地址的指针 我们来验证一下如图所示
obj
没进行赋值的时候分配了一块脏地址0x00000001002f6ec5
当objc
执行完calloc
方法返回内存地址0x00000001013595c0
说明已经开辟了内存 但是为什么和我们平常看到的不一样呢?
obj
没有和cls
进行关联绑定
3.关联到相应的类
进入 initInstanceIsa
这里探索究竟
inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
关联上以后打印obj
显示<LGPerson:0x1013595c0>
alloc
开辟内存 通过isa
指针与类进行关联
四、编译器优化
callAlloc 方法被编译器优化了
Build Settings->Optimization Level -> Fastest,Smallest(最快最小优化) 优化结果如下图所示
五、alloc的主线流程
六、字节对齐
uint32_t unalignedInstanceSize() const {
ASSERT(isRealized());
return data()->ro()->instanceSize;
}
对象的内存大小由什么决定? data()->ro()->instanceSize
成员变量
1.起初大小为8字节 因为NSObject 里面有成员变量 Class isa
Class-》isa结构体指针 所以是8字节
2.不满16 最少16
3.字节对齐
static inline size_t word_align(size_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
return (x + WORD_MASK) & ~WORD_MASK;
(8+7)&~7 = 15 & ~7
0000 1111
1111 1000 ~7
0000 1000 8
0000 0111 7
取8的整数 8字节对齐
为什么是8的倍数 不是16的倍数 或者32的倍数请看下节
cpu以8字节去读取 以空间换取时间 没有超过8字节的对象
七、字节对齐原理
字节对齐查看的3种方式
第一种方式如下图展示
第二种方式如下图展示
第三种方式查看如下图展示
x/6gx介绍 第一个x:16进制打印 6:打印6个 g: 8个字节 x:16进制变量显示 看下图:
1.当把height换成bool值的时候 如下图展示
2.当把height换成double值的时候 p/f 也可以直接打印double如下图展示