在面试的过程中,我们较大概率地会被问一个类所占的内存大小。本篇博客从下面一段测试代码开始分析整个内存大小的计算过程。
测试代码如下:
1 | struct A { |
执行结果(运行在模拟器下)如下:
其中class_getInstanceSize
指的是成员变量占用的内存大小,malloc_size
指的是指针指向内存空间的大小即实际分配的内存大小。
关于内存大小的计算主要依赖于运行环境以及内存对齐。上面的都是运行在arm64环境下,因此内存对齐决定了它们的值为什么不同。
内存对齐
内存对齐说白了就是为了提高CPU寻址操作性能的一种规则。我们可以通过#pragma pack(n)
,n=1、2、4、8、16 来改变这一系数,其中的n就是要指定的“对齐系数”。内存对齐的规则如下:
- 数据成员对齐规则:结构体或联合体的第一个数据成员放在偏移为0的位置,以后每个数据成员的位置为min(对齐系数,自身长度)的整数倍,下个位置不为本数据成员的整数倍位置的自动补齐。
- 数据成员为结构体:该数据成员的内最大长度的整数倍的位置开始存储。
- 整体对齐规则:数据成员按照1,2步骤对齐之后,其自身也要对齐,对齐原则是min(对齐系数,数据成员最大长度)的整数倍。
内存对齐计算
在64位编译器环境下
代码示例1:
1 | // 对齐系数为8 |
执行结果:
1 | Test1AA: 12 |
计算过程如下:
代码示例2:
1 |
|
执行结果:
1 | Test2AA: 32 |
计算过程如下:
OC类的内存分析
相关函数
class_getInstanceSize
上面讲到class_getInstanceSize
表示成员变量所占的内存大小,那么对于Person类创建的实例来说,它的成员变量大小应该为12,为什么结果却是16。
class_getInstanceSize
的内存也有它自己的内存对齐,通过objc源码中的class_getInstanceSize
的底层实现可以知道,class_getInstanceSize
的实现依赖于底层函数word_align
,该函数返回的结果是8的倍数,另外从alloc
函数开始进行分析,到instanceSize
函数中可以知道所有对象的内存大小至少是16个字节,所以对象申请的内存空间是以8字节进行内存对齐且至少是16个字节。
word_align
实现如下:
1 | static inline uint32_t word_align(uint32_t x) { |
malloc_size
通过malloc源码中的segregated_size_to_fit
函数可以知道系统开辟内存空间是以16字节进行内存对齐。
1 | static MALLOC_INLINE size_t |
Person、Student的内存分析
回到一开始的测试代码,根据上面内存对齐的3个规则,Person、Student1、Student2的实际分配内存(malloc_size)计算过程如下:
使用View Memory查看内存
从上面这张图中我们可以知道,两个16进制代表1个字节,一行有32个字节。01 00 00 00
、03 00 00 00
、05 00 00 00
(小端)对应的就是_a
、_b
、_c
,另外前8个字节就是isa。整体内存布局也与我们手动计算实际内存分配的结果一致。
sizeof
sizeof
用来返回类型的大小,其内部也是进行了内存对齐的。将测试代码中的结构体A
、AA
、AAA
通过内存对齐规则进行分析,确实得到0
,1
,8
。
这里我们使用结构体AA
进行举例:
- 根据规则1,数据成员占1个字节,位于0号地址;
- 无结构体成员变量,跳过规则2;
- 根据规则3,
min(1, n) = 1
,取1的整数倍,即结构体分配的内存大小为1个字节。
关于使用sizeof
去获取OC类内存大小的时候,我发现一个比较有意思的东西。
测试代码如下:
1 | struct N_NSObject_IMPL { |
执行结果:
N_NSObject_IMPL
、N_Person_IMPL
、N_Student_IMPL
是将OC转成C++代码时对应的结构,使用sizeof
获取这些结构体大小的时候,值与class_getInstanceSize
的值是一样的。
使用sizeof
获取指针、实例、类其结果都是8,这又是为什么呢?个人认为传实例和类的时候可以看做传的其实就是对应的指针。指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,地址基本就是整形,因此无论用什么类、对象作为sizeof
的参数(这里就将sizeof
看成是一个函数),其结果都一样的。
最后再总结下class_getInstanceSize
、malloc_size
、sizeof
的区别:
class_getInstanceSize
表示成员变量的所占的内存大小malloc_size
表示实际分配的内存大小sizeof
表示变量或者类型的大小,传入结构体,返回的则是结构的大小,传入指针(这里的指针表示C指针,OC的引用)即传入值,则返回传入值的类型大小