背景
在上篇对象alloc那点事中遗留了一个对象带有基础类型属性后占用内存大小的问题。另外参考之前分析的对象开辟内存的调用过程如下:
//确认创建类需要开辟的内存大小
size = cls->instanceSize(extraBytes);
//开辟内存
obj = (id)calloc(1, size);
//把isa信息填充到上面申请到的内存中
obj->initInstanceIsa(cls, hasCxxDtor);
开辟内存的calloc方法需要传入的size非常关键,这个size是通过instanceSize
计算得出的,而分析 instanceSize
流程发现,它是拿对象的 实际占用大小
进行16字节对齐后得到 实际分配大小
。
所以关键问题就到了如何计算 实际占用大小
!大家知道OC类经过编译后会生成相应的结构体,所以想知道OC对象的 实际占用大小
其实就是研究结构体对象在内存中分布以及对齐方式
。
一、OC类示例
前面提到对象的 实际占用大小
和 实际分配大小
,这两者有什么区别呢,先看一个例子:
- 实际占用大小40:8(isa) + 8(name) + 8(nickName) + 4(int) + 8(double) + 1(c1) + 1(c1) = 38,那为什么打印出来是40呢,那是因为结构体对象在底层需求经过
字节对齐
。- 实际分配大小48:40再按16字节对齐(苹果的特性,所有对象最后都按16字节对齐),就是48。
二、结构体字节对齐
1.结构体对齐的原则
结构体对象在进行字节对齐时会遵从以下三点原则:
- 数据成员 : 结构体中第一个数据成员放在
offset为0
的地方,以后每个数据成员存储的起始位置
要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍
开始。 - 结构体作为成员 : 如果一个结构里有某些结构体成员,则结构体成员要从
其内部最大元素大小的整数倍
地址开始存储(struct a里存有struct b,b
里有char、int 、double等元素,那b应该从8的整数倍开始存储)。 - 结构体整体对齐 : 结构体的总大小,也就是sizeof的结果,必须是其内部
最大 成员的整数倍
,不足的要补⻬。
2.结构体对齐举例
结构体字节对齐关键点有两个:
- 下一个成员存储的开始位置判断,它必须是该成员自身长度的倍数。
- 最后的收尾,必须是最大成员长度整数倍。
3.嵌套结构体对齐举例
嵌套结构体
- 先计算LGStruct2中a、b、c、d起始位置和大小,共16字节。
- str1开始位置的index为16,是其内部最大成员double的整数位,所以开始位置不变。
- LGStruct1的大小为24(同样的计算逻辑),从index16开始24个字节存str1。
- 总大小为16+24 = 40,再看是否最大成员的整数倍(这里要把被嵌套的结构体成员一起平铺来看)
再来个复杂点的
嵌套结构体三、再看OC类
1.编译器优化
不知道大家发现没有,上面结构体对齐举例中单个LGStruct1和LGStruct2它们只是int和char的位置不同,但求出来的sizeof却不一样。如果真的都24字节来开辟内存,那对系统来说当然是一种浪费,所以在系统底层会对结构体成员进行优化,减小其开辟内存的大小。
不多解释了,注释已经很清楚
2.看一下OC类的大小
定义一个LGPerson类
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) double height;
@property (nonatomic) char char1;
@property (nonatomic, assign) int age;
@property (nonatomic) short shot1;
@end
OC对象
class_getInstanceSize
: 获取oc类在底层对应结构体的实际占用内存大小
malloc_size
: 获取系统层面对该对象实际分配的内存大小
class_getInstanceSize分析
先看一下class_getInstanceSize
源码
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
static inline size_t word_align(size_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
define WORD_MASK 7UL
为什么要单独把
class_getInstanceSize
方法拿出来讲一下呢,因为它内部进行字节对齐的代码非常经典(x + WORD_MASK) & ~WORD_MASK
。
- 如果x是8的整数倍,则转换成2进制后,其低三位一定是000,加上 WORD_MASK(二进制为111),再&上WORD_MASK取反,就是把低3位置0,刚好保持了原x的值不变。
- 如果x不是8的整数倍,则其低三3中一定有一个1,加上WORD_MASK(二进制为111)后一定会导致其第四位进位,再&上WORD_MASK取反,则只保留了8的整数倍,也确保了值比x大,所以实现了以8字节对齐的逻辑。
最后
虽然看起来OC对象大小的计算很简单,但在真实场景下我们的对象属性之多,结构之复杂要远超上面的例子,这里只是探究了OC对象在创建过程中的冰山一角。好了,今天就先到这吧~~
未完待续...
网友评论