对象实例
在上一节alloc的源码分析中看到,成员变量是以8字节内存对齐的。我们来验证一下 先声明一个Person的类
@interface Person : NSObject
@end
打印如下数据:
Person *person = [Person alloc];
NSLog(@"%@ - %lu - %lu - %lu",person, sizeof(person),class_getInstanceSize([person class]),malloc_size((__bridge const void *)(person)));
打印的结果如下:
sizeof(person),我们来看一下person,person是一个Person *类型的。Person继承与NSObject
- sizeOf:
sizeof
计算数据(数组,变量,类型,结构体等)所占内存空间,单位字节
- class_getInstanceSize:计算对象及成员变量占用的内存空间,需要注意
父类属性
和isa
,8
字节对齐 - malloc_size:
malloc_size
计算实际向系统申请开辟的内存空间,16
字节对齐
@interface NSObject <NSObject> {
....
Class isa OBJC_ISA_AVAILABILITY; //有一个isa
....
}
//class是一个结构体指针的类型
typedef struct objc_class *Class;
所以上面的sizeof(person)就是结构体指针的大小,故为8. 我们来看class_getInstanceSize([LGPerson class])为8,
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
我们看一下cls->alignedInstanceSize()
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() const {
ASSERT(isRealized());
return data()->ro()->instanceSize;
}
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
//x + 7 & ~7 这是一个以8对齐的操作
}
由于person中没有成员变量和属性,只有一个isa所以还是8. 那我们加入两个属性
@interface Person : NSObject
@property (nonatomic, copy) NSString *name; // 8
@property (nonatomic, assign) NSInteger age; //4
@end
8 + 4 + 8(isa) = 20, 8字节对齐,所以是24。
分别添加方法、成员变量。看看是否影响对象实例的大小
@interface Person : NSObject {
NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
+(void)test;
-(void)test;
@end
实验证明,成员变量、属性可以影响对象实例的大小,方法是不影响的。
属性是由成员变量、存取方法组合而成,故只有成员变量影响对象实例的大小。
对象的内存以16字节对齐
malloc_size((__bridge const void *)(person)) 对象分配的内存是以16字节为单位对齐的,maclloc源码分析函数调用流程图如下:
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
}
//以16字节对齐
//k = (size + 15) >> 4
k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
//slot_bytes = k << 4
slot_bytes = k << SHIFT_NANO_QUANTUM; // multiply by power of two quanta size
*pKey = k - 1; // Zero-based!
return slot_bytes;
}
注:
在源码跟踪过程中ptr = zone->calloc(zone, num_items, size)
点开zone->calloc
//是一个函数定义
void *(* MALLOC_ZONE_FN_PTR(calloc))(struct _malloc_zone_t *zone, size_t num_items, size_t size); /* same as malloc, but block returned is set to zero */
此时可以用汇编,或者打印po/p zone->calloc 打印出复制的函数为default_zone_calloc
同理,default_zone_calloc中也可打印出所指向函数
po zone->calloc
(.dylib`nano_calloc at nano_malloc.c:878)
结构体对齐原则
数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第 一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要 从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组, 结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存 储。 min(当前开始的位置mn)m=9 n=4 9 10 11 12
结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从 其内部最大元素大小的整数倍地址开始存储. (struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.) 必须是其内部最大成员的整数倍,不足的要补⻬。
收尾工作:结构体的总大小,也就是sizeof的结果,
struct LGStruct1 {
double a; // 8 [0 7]
char b; // 1 [8]
int c; // 4 (9 10 11 [12 13 14 15]
short d; // 2 [16 17] 24
}struct1;
struct LGStruct2 {
double a; // 8 [0 7]
int b; // 4 [8 9 10 11]
char c; // 1 [12]
short d; // 2 (13 [14 15] 16
}struct2;
NSLog(@"%lu-%lu",sizeof(struct1),sizeof(struct2));
// 24, 16