alloc 分析
LGPerson *p1 = [LGPerson alloc];
id p2 = [p1 init];
id p3 = [p1 init];
id p4 = [LGPerson alloc];
id p5 = [LGPerson alloc];
NSLog(@"\n%@\n%@\n%@\n%@\n%@",p1,p2, p3, p4, p5);
<LGPerson: 0x100663f30>
<LGPerson: 0x100663f30>
<LGPerson: 0x100663f30>
<LGPerson: 0x100663f90>
<LGPerson: 0x100664030>
对于alloc 系统会创建一个内存对象,并在栈中创建一个对象指针只想对象的地址空间。
init操作不会创建新的对象空间。
_objc_rootAlloc -> callAlloc -> callAlloc
- hasCustomAWZ 代表类有没有实现类方法 + (instancetype)allocWithZone:(struct _NSZone *)zone 如果有这回发送 allocWithZone消息。没有着会去执行_objc_rootAllocWithZone。
- slowpath 和fastpath 是编译器优化,大概率执行的优先编译指令
// [[cls all] init] new 会调用 objc_alloc_init(Class cls) 方法.再断点调试是可以显示符号
id objc_alloc_init(Class cls)
{
return [callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/) init];
}
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// 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));
}
_objc_rootAllocWithZone -> _class_createInstanceFromZone 其中会具体地初始化一个类
其中有几个重点关注的地方
- 计算内存大小 size = cls->instanceSize(extraBytes);
- 分配内存空间 obj = (id)calloc(1, size);
- 初始化isa obj->initInstanceIsa(cls, hasCxxDtor);
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);
}
//
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
//hasCxxCtor:类及父类是否有自己的构造函数;
//hasCxxDtor:类及父类是否有自己的析构函数(这个条件在后面讲对象dealloc的时候也会说到,与对象是否有实例变量有关,这条件会记录在对象的isa内);
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
// 使用可以用纯指针表示比如没有实例方法和实例属性的类
bool fast = cls->canAllocNonpointer();
size_t size;
// 获取尺寸, word_align 计算的数据
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
// 分配内存 zone 已经废弃
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);
}
instanceSize
返回的一个对象最少需要的内存大小。分配内存带下的计算:
- 内存大小的计算。是根据具体的内核决定,然后是返回 8/16 的整数倍的内存大小。返回的一个对象最少需要的内存大小
- 内存大小和类的成员变量有关ivar
调试: x obj 可以打印obj所在内存的内存数据(可能是指针地址,或者数据)。 x/nxg n 表示个数。可以用来显示n个已经组合好的地址数据。
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() const {
ASSERT(isRealized());
return data()->ro()->instanceSize;
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
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;
}
对象的成员数据位置和大小的检测
@interface LGPerson : NSObject
@property (strong, nonatomic) NSString * name;
@property (assign, nonatomic) int age;
@property (assign, nonatomic) int xx;
@property (assign, nonatomic) NSString * flag;
@end
LGPerson *p = [LGPerson alloc] ;
p.name = @"明星";
p.age = 10;
p.flag = @"asdasdasd";
p.xx = 102;
(lldb) x/4xg p
0x10069e4b0: 0x001d8001000022ed 0x000000660000000a
0x10069e4c0: 0x0000000100001010 0x0000000100001030
0x00000066 : 102
0x0000000a : 10
0x0000000100001010: 明星
0x0000000100001030:asdasdasd
// 说明属性的存储排序不是按照定义的排序,而是进行了优化。
// int四个字节,两个int组合占据8个字节
字节对齐(struct 中的计算)
其实字节对齐的细节和具体编译器实现相关,但一般而言,满足三个准则:
- 数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第⼀个数据成员放在offset为0的地⽅,以后每个数据成员存储的起始位置要从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组,结构体等)的整数倍开始(⽐如int为4字节,则要从4的整数倍地址开始存储。 min(当前开始的位置m n)
- 结构体作为成员:如果⼀个结构⾥有某些结构体成员,则结构体成员要从其内部最⼤元素⼤⼩的整数倍地址开始存储.(struct a⾥存有struct b,b⾥有char,int ,double等元素,那b应该从8的整数倍开始存储.)
- 收尾⼯作:结构体的总⼤⼩,也就是sizeof的结果,.必须是其内部最⼤成员的整数倍.不⾜的要补⻬。
struct P1 {
BOOL sex; // 2
NSString * name1; // 8
int age; // 4
};// 2 + (6 第二个成员8个字节,起始位置从8的整数倍开始开始 ) + 8 + 4 = 20 -> 8(最大成员的)的整数倍 24
struct P2{
int age; // 4
NSString * name1; // 8
BOOL sex; // 2
};// 4 + (4 第二个成员8个字节,起始位置从8的整数倍开始开始 ) + 8 + 2 = 18 -> 8(最大成员的)的整数倍 24
struct P3{
BOOL sex; // 2
int age; // 4
NSString * name1; // 8
};// 2 + (2) + 4 + 8 = 16 -> 16
struct P1 p1 ;
struct P2 p2 ;
struct P3 p3 ;
NSLog(@"%ld %ld %ld", sizeof(p1), sizeof(p2), sizeof(p3));
// 输出 24 24 16
struct T1{
int a; // 8
char b;
struct P1 p;// 24 最大成员8
};// 4 + 2 + (2) + 24 = 32 -> 32
struct T2{
char b;
struct P1 p;// 24 最大成员8
};// 2 + (6 P1 的最大成员是8 起始从8的倍数开始) + 24 = 32 -> 32
struct T3{
int a; // 8
char b;
struct P4 p4; // 8 最大成员是4
};// 4 + 2 + (2) + 8 = 16 -> 16 最大成员是4 4的倍数
struct T4{
char b;
struct P4 p4; // 8 最大成员是4
};// 2 + (2) + 8 = 12 -> 最大成员是4,所以是4的倍数。12
calloc 内存分配
在libmalloc中。分配内存按照16字节进行分配。根据前面计算的大小 size。
(size + 15) >> 4 // 计算大于size的16的最小整数倍
initInstanceIsa
主要负责初始化 对象的isa数据。
alloc过程中对象isa的初始化
init
对于init函数,
总结
malloc的执行流程

- 对象大小的分配以及 字节对齐
- 分配空间和16字节对齐
- 初始化isa
参考:
__builtin_expect
cpu 的去指令和执行指令是并行的。如果执行指令后发现要跳转。可能要重新取指令降低效率。所以__builtin_expect可以设置一个概率比较大的跳转的位置,提高取指令执行的效率。
// 比如。bool运算下可能下面的第二条指令执行的概率高。设置1。直接选取第二条执行。减少再次取指令的几率。
#define fastpath(x) (__builtin_expect(bool(x), 1))
网友评论