言归正传,以下代码,系统到底帮我们做了些什么?
NSObject *object = [[NSObject alloc] init];
通过苹果官方的开源代码,我们可以看得到内部的实现机制;
官方地址:opensource.apple.com
配置调试:https://blog.youkuaiyun.com/myiphon/article/details/105014942
可编译的779版本:https://github.com/DuWen/Objc4
Alloc
- _objc_rootAlloc
- callAlloc
- allocWithZone
- _objc_rootAllocWithZone
- _class_createInstanceFromZone
/***********************************************************************
* class_createInstance
* fixme
* Locking: none
*
* Note: this function has been carefully written so that the fastpath
* takes no branch.
**********************************************************************/
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);
}
在这个函数里,通过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;
}
# define WORD_MASK 7UL
为了提高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并未做任何操作。。。
验证
新建个Person类,里面有age、name属性
@interface Person : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, copy) NSString *name;
@end
打印下实例对象的成员变量多占大小以及指针指向的内存大小
Person *person = [[Person alloc] init];
NSLog(@"%zd", class_getInstanceSize([person class]));
NSLog(@"%zd", malloc_size((__bridge const void *)person));
// 打印信息
2020-04-07 17:38:19.974677+0800 objc-debug[23494:214167] 24
2020-04-07 17:38:19.975183+0800 objc-debug[23494:214167] 32
实例对象的isa指针 占8个字节
age是Int类型 占4个字节
name是String类型 占 8个字节
总共20个字节,那怎么会输出24呢?
上面分析源码时,发现有字节对齐的操作,那我们来试试字节对齐后的值是多少
20转成二进制是:00010100 代入公式计算
(0001 0100 + 0000 0111) & 1111 1000 = 0001 1000 也就是24
那指针指向的内存大小又为啥是32呢?
在上面的源码中,我们发现这么一段代码
obj = (id)calloc(1, size);
calloc到底做了些什么骚操作,改变了内存大小,通过符号断点可以跟踪到malloc源码,这也是苹果开源的代码 libmalloc-283.40.1
calloc
- calloc
- malloc_zone_calloc
- zone->calloc(zone, num_items, size)
- default_zone_calloc (malloc.c)
- zone->calloc
- nano_calloc (nano_malloc.c)
- _nano_malloc_check_clear
- segregated_size_to_fit
最后定位到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;
}
#define SHIFT_NANO_QUANTUM 4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) // 16
根据上面的公式计算
(24 + 16 - 1)>>4<<4 也就是32!!!
也就是说对原始内存大小做了16字节对齐,确保了是16的倍数。