本次底层探索根据当前面试中常遇到的一些底层问题主要探讨以下几个问题:
- 1.内存地址长什么样?
- 2.[[class alloc]init] 中alloc 与init 分别做了那些工作
- 3.Xcode内存对齐与优化
内存地址长什么样?
- 首先我们知道地址编译出来是16进制的数字,16进制表示内存地址也是一种约定俗成的方式,因为16进制和二进制转换容易,一位就是4个二进制位,与或运算一眼就能看结果。
-
内存地址的作用: 在8086的实模式下,把某一段寄存器左移4位,然后与地址ADDR相加后被直接送到内存总线上,这个相加后的地址就是内存单元的物理地址,而程序中的这个地址就叫逻辑地址(或叫虚地址)。在80386的保护模式下,这个逻辑地址不是被直接送到内存总线,而是被送到内存管理单元(MMU)。MMU由一个或一组芯片组成,其功能是把逻辑地址映射为物理地址,即进行地址转换。
[[class alloc]init] 中alloc 与init 分别做了那些工作
DNClass * cls = [[DNClass alloc]init];
DNClass * cls1 = [cls init];
DNClass * cls2 = [cls init];
NSLog(@"==cls==%@==%p==", cls, &cls);
NSLog(@"==cls1==%@==%p==", cls1, &cls1);
NSLog(@"==cls2==%@==%p==", cls2, &cls2);
==cls ==<DNClass: 0x1c423f160>==0x16fce74f8==
==cls1==<DNClass: 0x1c423f160>==0x16fce74f0==
==cls2==<DNClass: 0x1c423f160>==0x16fce74e8==
通过这段代码,我们可以看出s1、s2、s3的指针地址虽然不同,但是指向的对象却是同一个,也就是同一片内存空间。
结合objc源码我们来看下alloc 的底层实现
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));
}
OBJC2 中有2个宏定义我们这里简单介绍下
#define fastpath(x) (__builtin_expect(bool(x), 1)) #define slowpath(x) (__builtin_expect(bool(x), 0)) slowpath 当前判断为0 的可能性更大 fastpath 当前判断为1 的可能性更大
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);
}
接下来就到了我们的重点部分,这里主要做了3件事情:
- 计算要开辟空间的size 大小
- calloc 开辟空间 创建obj
- obj->initIsa 将cls 与 obj 关联
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);
}
isa_t 结构体
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8; // defined in isa.h
};
#endif
};
nonpointer
0,代表普通的指针,存储着Class、Meta-Class对象的内存地址
1,代表优化过,使用位域存储更多的信息
has_assoc
是否有设置过关联对象,如果没有,释放时会更快
has_cxx_dtor
是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
shiftcls
存储着Class、Meta-Class对象的内存地址信息
magic
用于在调试时分辨对象是否未完成初始化
weakly_referenced
是否有被弱引用指向过,如果没有,释放时会更快
deallocating
对象是否正在释放
has_sidetable_rc
引用计数器是否过大无法存储在isa中如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
extra_rc 里面存储的值是引用计数器减1
.Xcode内存对齐与优化
在 _class_createInstanceFromZone中通过instanceSize来分配内存大小
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;
}
可以看出不足16字节的默认设置了16字节的大小,也就是说一个对象的创建至少占16个字节,而且size做了字节对齐操作
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
static inline size_t word_align(size_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
为了提高CPU的访问效率,这里保证了Size是8的倍数。 x + WORD_MASK 保证了不足8位需要进位 & ~WORD_MASK 保证后三位是0 也就是8的倍数
通过calloc分配所需的内存空间,并返回一个指向它的指针,查看源码的时候看到了calloc、malloc、realloc,那他们有什么区别呢?以下是定义的函数原型
void *malloc(size_t __size) __result_use_check __alloc_size(1);
void *calloc(size_t __count, size_t __size) __result_use_check __alloc_size(1,2);
void *realloc(void *__ptr, size_t __size) __result_use_check __alloc_size(2);
calloc & malloc : 分配所需的内存空间,并返回一个指向它的指针 realloc:尝试重新调整之前调用 malloc 或 calloc 所分配的 ptr 所指向的内存块的大小
区别: 1、malloc 不会设置内存为零,而 calloc 会设置分配的内存为零。 2、malloc与calloc用来动态分配内存空间,而realloc则是对给定的指针所指向的内存空间进行扩大或者缩小。
init
_objc_rootInit
id _objc_rootInit(id obj)
{
// In practice, it will be hard to rely on this function.
// Many classes do not properly chain -init calls.
return obj;
}
可以看出init并未做任何操作