对象size那点事(二)

2020-09-09  本文已影响0人  会跑的鱼_09

背景

在上篇对象alloc那点事中遗留了一个对象带有基础类型属性后占用内存大小的问题。另外参考之前分析的对象开辟内存的调用过程如下:

//确认创建类需要开辟的内存大小
size = cls->instanceSize(extraBytes);
//开辟内存
obj = (id)calloc(1, size);
//把isa信息填充到上面申请到的内存中
obj->initInstanceIsa(cls, hasCxxDtor);

开辟内存的calloc方法需要传入的size非常关键,这个size是通过instanceSize计算得出的,而分析 instanceSize 流程发现,它是拿对象的 实际占用大小 进行16字节对齐后得到 实际分配大小
所以关键问题就到了如何计算 实际占用大小 !大家知道OC类经过编译后会生成相应的结构体,所以想知道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.结构体对齐的原则

结构体对象在进行字节对齐时会遵从以下三点原则:

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对象在创建过程中的冰山一角。好了,今天就先到这吧~~

未完待续...

上一篇 下一篇

猜你喜欢

热点阅读