前言导入:
对于[[Class alloc] init], [Class new]相信大家都熟悉的不能再熟悉了吧,今天带大家深入了解一下alloc、init、new的底层原理吧
。
问题抛出:
Person *p1 = [LGPerson alloc];
Person *p2 = [p1 init];
Person *p3 = [p1 init];
LGNSLog(@"%@ - %p - %p",p1,p1,&p1);
LGNSLog(@"%@ - %p - %p",p2,p2,&p2);
LGNSLog(@"%@ - %p - %p",p3,p3,&p3);
对于以上的代码,大家先思考下,输出的语句是什么....?
<Person: 0x600002bdc130> - 0x600002bdc130 - 0x7ffeea65c178
<Person: 0x600002bdc130> - 0x600002bdc130 - 0x7ffeea65c170
<Person: 0x600002bdc130> - 0x600002bdc130 - 0x7ffeea65c168
对于以上的结果,我们要思考下为什么,得到的是这样的结果
分析:
1.输出的第一项是对象的描述;输出的第二项是对象指针指向的地址;输出的第三项是指针本身的地址
2.由输出结果可知,p2,p3的指针指向的是同一地址,也就是p1,那么猜想init实际上并没有做任何操作。
探究过程:
了解alloc底层的原理
首先我们command
进alloc
的代码,发现进入到
+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
这不是我们想要的结果,苹果对于此api已经封装起来,无法进入到内部,所以我们只能去苹果开源
找对应的api源码了
首先我们得确认找哪个方向的库文件,然后才能找到源码。
Step1:
首先我们在Person *p1 = [Person alloc];
打下断点,然后打开Xcode->Debug->Debug WorkFlow->Alsways Show Disassembly,
运行项目
0x10f16bed6 <+54>: movq 0x5963(%rip), %rsi ; (void *)0x000000010f171938: Person
0x10f16bedd <+61>: movq %rsi, %rdi
0x10f16bee0 <+64>: callq 0x10f16de78 ; symbol stub for: objc_alloc
0x10f16bee5 <+69>: movq %rax, -0x28(%rbp)
0x10f16bee9 <+73>: movq -0x28(%rbp), %rax
0x10f16beed <+77>: movq %rax, %rdi
我们截获到objc_alloc
,接着我们打下符号断点,输入objc_alloc
,接着运行,看到断点跑到
libobjc.A.dylib`objc_alloc:
-> 0x7fff514122ac <+0>: testq %rdi, %rdi
0x7fff514122af <+3>: je 0x7fff514122cb ; <+31>
0x7fff514122b1 <+5>: movq (%rdi), %rax
0x7fff514122b4 <+8>: testb $0x40, 0x1d(%rax)
0x7fff514122b8 <+12>: jne 0x7fff5140a138 ; _objc_rootAllocWithZone
0x7fff514122be <+18>: movq 0x389f4243(%rip), %rsi ; "alloc"
0x7fff514122c5 <+25>: jmpq *0x36623fc5(%rip) ; (void *)0x00007fff513f7780: objc_msgSend
0x7fff514122cb <+31>: xorl %eax, %eax
0x7fff514122cd <+33>: retq
从以上可知,我们需要在libojbc.A.dylib
中去查找源码,
Step2:
在苹果开源OpenSource
中取查找我们的objc库,苹果开源链接,接着我们找到对应的objc,进行下载
下载完之后,配置好后,我们打开项目。
Step3:
在下载完的库代码中,一步一步执行断点,发现断点的踪迹:
初始断点处,项目继续执行
Person *objc1 = [Person alloc];
项目进入到alloc的执行代码块
+ (id)alloc {
return _objc_rootAlloc(self);
}
接着来到_objc_rootAlloc执行代码块
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
接着来到callAlloc执行代码块,是不是有点印象呀,之前我们就是依靠符号callAlloc断点来锁定的,这也和我们之前项目的代码断点不谋而合**
// shortcutting optimizations.
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));
}
继续
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:此处已经来到了最深处的核心实现代码,我们就这个核心代码来进行分析,来看看alloc 是怎么进行工作的
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 {
// alloc 开辟内存的地方
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);
}
image.png
image.png
alloc的流程总结:
alloc -> _objc_rootAlloc -> callAlloc -> _objc_rootAllocWithZone -> _class_createInstanceFromZone
_class_createInstanceFromZone
核心代码的三部曲:
1.计算内存大小
size_t size;
size = cls->instanceSize(extraBytes);
2.申请开辟内存,返回地址指针
obj = (id)calloc(1, size);
3.将isa指针和cls类进行关联
obj->initInstanceIsa(cls, hasCxxDtor);
核心知识:
cls->instanceSize(extraBytes):运用16字节对齐的方式返回分配的字节数
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.
if (size < 16) size = 16;
return size;
}
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);
}
}
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
calloc(1, size):运用16字节对齐的方式返回分配的字节数
command
进入calloc
方法,step_into
进入查找到malloc_zone_calloc
的方法;
libsystem_malloc.dylib`malloc_zone_calloc:
0x7fff6a1b8ef6 <+0>: pushq %rbp
-> 0x7fff6a1b8ef7 <+1>: movq %rsp, %rbp
0x7fff6a1b8efa <+4>: pushq %r15
0x7fff6a1b8efc <+6>: pushq %r14
0x7fff6a1b8efe <+8>: pushq %r12
0x7fff6a1b8f00 <+10>: pushq %rbx
进入苹果开源中找到对应的库文件,下载并打开,
image.png
获取到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;
}
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;
}
initInstanceIsa:将指针和cls类进行关联:
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
未完待续》。。。
更新:
isa_t newisa(0);
首先我们来分析下isa结构体
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
此结构体为联合体位域结构体,利用字节分配空间,大大地节省了isa指针的内存空间,即8*8的64位
其中最重要的要属"shiftcls",存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位⽤来存储类指针
想当然的是,我们可以依赖于isa的字节,取出里面对应的33字节,来验证我们得出的结论。
验证:
image.pngstep1:打印出objc1的内存分部
step2:打印出objc1的内存分部,以16进制的形式输出,第一个8位即isa指针,即isa指针中的bits
step3: bits先右移3位,即抹掉 uintptr_t nonpointer(1位)、 uintptr_t has_assoc( 1位 ) uintptr_t has_cxx_dtor (1位)
step4: 接着向右移动20位,抹掉后面的17位,再加上之前右移的3位,还原,这样就相当于吧shitlCls的前后部分都给抹掉了
step5: 最后将位置还原,这样即得到了shitCls的字节,然后我们输出得到了Class信息
step10:或者我们直接拿到isa的bits为,与我们的面具0x00007ffffffffff8ULL 进行与算法,这样也可以得到我们的shitCls的信息
得出结论:
所以我们的isa指针
中与我们的类信息Class
进行了关联
总结:
alloc的核心三部曲:
1.计算内存空间
2.分配内存空间,返回isa指针
3.将isa指针与Cls进行关联
课外进阶:
- 结构体内存的计算:
结构体指针
占8个字节,但是结构体本身
占多少字节内存呢,这个就依赖于结构体的成员属性
来决定了!!!!!
-
规则如下:
1:数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第 ⼀个数据成员放在offset为0的地⽅,以后每个数据成员存储的起始位置要从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组,结构体等)的整数倍开始(⽐如int为4字节,则要从4的整数倍地址开始存储。 min(当前开始的位置m n) m = 9 n = 4 9 10 11 12
2:结构体作为成员:如果⼀个结构⾥有某些结构体成员,则结构体成员要从其内部最⼤元素⼤⼩的整数倍地址开始存储.(struct a⾥存有struct b,b⾥有char,int ,double等元素,那b应该从8的整数倍开始存储.)
3:收尾⼯作:结构体的总⼤⼩,也就是sizeof的结果,.必须是其内部最⼤ 成员的整数倍.不⾜的要补⻬。
-
数据类型占字节大小:
image.png
-
example1:
计算以下结构体占多少内存
struct LGStruct1 {
double a;
char b;
int c;
short d;
}struct1;
分析:
struct LGStruct1 {
double a; step1: [0,1,2,3,4,5,6,7] 占8字节
char b; [8] 占1字节
int c; [9,10,11,12] ->[12,13,14,15] 占4字节
short d; [16,17] 占2字节
}struct1;
内部需要的大小为: 18
结构体整数倍: 3*8=24
但是最后要是最大成员的整数倍,也就是 24 字节
-
example2:
计算以下结构体占多少内存
struct LGStruct2 {
double a; 8 (0-7)
int b; 4 (8 9 10 11)
char c; 1 (12)
char e ; 1 (13)
char f; 1 (14)
short d; //2 (16,17)
}struct2;
内部需要的大小为: 18
结构体整数倍: 3*8 - 24
但是最后要是最大成员的整数倍,也就是 24 字节
-
example2:
计算以下结构体占多少内存
struct LGStruct3 {
double a; step1: [0,1,2,3,4,5,6,7] 占8字节
char b; [8] 占1字节
struct LGStruct2 stru; 内部最大的成员占8字节,所以要从8的倍数开始,也就是 16 开始 [16 ... 33] 占18字节
int c; [36,37,38,39] 占4字节
short d; [40,41] 占2字节
}struct3;
等价于
struct LGStruct3 {
double a; step1: [0,1,2,3,4,5,6,7] 占8字节
char b; [8] 占1字节
struct LGStruct2 {
double a; 8 (0-7) 从内部成员最大的整数倍开始[16,17,18,19,20,21,22,23]
int b; 4 (24 25 26 27)
char c; 1 (28)
char e ; 1 (29)
char f; 1 (30)
short d; 2 (32,33)
}struct2;
int c; [36,37,38,39] 占4字节
short d; [40,41] 占2字节
}struct3;
内部需要的大小为: 42
但是最后要是最大成员的整数倍,也就是 8*6= 48 >42字节
输出48
网友评论