本页所使用的objc runtime 756.2,来自GITHUB
开始继续学习研究OC源码,今天看的是OC的内存原理,以及对齐原理的分析。
1.概念
内存对齐在数据结构中,是比较基础也比较重要的一环,对于iOS开发,研究底层更免不了要了解他们的原理,方便我们更透彻的学习iOS的原理。
我们先来一段维基百科的数据结构对齐的相关知识。
内存对齐,也可以理解为数据结构对齐(Data structure alignment),是代码编译后在内存的布局与使用方式。包括三方面内容:数据对齐、数据结构填充(padding)与包入(packing)。
下图是内存中各类型属性所占的空间大小:
列举一下各数据类型所占字节(32位):
- A char (one byte) will be 1-byte aligned.
- A short (two bytes) will be 2-byte aligned.
- An int (four bytes) will be 4-byte aligned.
- A long (four bytes) will be 4-byte aligned.
- A float (four bytes) will be 4-byte aligned.
- A double (eight bytes) will be 8-byte aligned on Windows and 4-byte aligned on Linux (8-byte with -malign-double compile time option).
- A long long (eight bytes) will be 4-byte aligned.
- A long double (ten bytes with C++Builder and DMC, eight bytes with Visual C++, twelve bytes with GCC) will be 8-byte aligned with C++Builder, 2-byte aligned with DMC, 8-byte aligned with Visual C++, and 4-byte aligned with GCC.
- Any pointer (four bytes) will be 4-byte aligned. (e.g.: char*, int*)
需要注意的是,在64位机上,有以下不同
- A long (eight bytes) will be 8-byte aligned.
- A double (eight bytes) will be 8-byte aligned.
- A long long (eight bytes) will be 8-byte aligned.
- A long double (eight bytes with Visual C++, sixteen bytes with GCC) will be 8-byte aligned with Visual C++ and 16-byte aligned with GCC.
- Any pointer (eight bytes) will be 8-byte aligned.
2. 原则
数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储。
结构体作为成员: 如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
**收尾工作:**结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补齐.
3. 实践
3.1 属性8字节对齐:
当创建对象时,其属性的按类型占据不同的内存空间,但是由于大小不一,需要进行补齐;原则则是以第一位补齐为8的倍数(64位系统是8为对齐,而32位系统是4位对齐)。
以下面的代码举例子
struct MyStruct { |
// 我们打印一下各自的所占空间
NSLog(@"%lu -- %lu",sizeof(MyStr1), sizeof(MyStr2)); |
结果如下:
为何两者不一致?
我们来看第一个结构体对象的内部结构,排第一的属性double占8位,而char需要补齐7位成为8位,而剩余的int,short 则分别占4位,不需补齐,可以灵活堆在同一个内存区域。
struct MyStruct { |
总计是 8 + 8 + 4 + 4 = 24;
而第二个结构体对象,排第一的属性double占8位,而剩余的int,short ,short 总计都,不需补齐,可以灵活堆在同一个内存区域。
struct MyStruct2 { |
这里后3位,可以放到一个8位内存区间,即4 + 1 + 2,只需补齐1位至8位,可以灵活排列在一个内存空间内,所以本结构体对象总占内存为8+4+1+2 + 1(补齐1位) = 16。
如果没有弄懂,我们找出源码来读一读:(/objc/Private Headers/Project Headers/objc-os.h)
创建对象 alloc
alloc方法的实现——返回rootAlloc方法
返回callAlloc的实现
当缓存区没有对象时,创建对象
去内存区域创建对象
执行类的属性方法,确定创建的空间大小
对齐内存
内存对齐的返回:类的属性大小取决于指针大小
8字节对齐:实现方法
3.2 对象16字节对齐
在OC 底层研究1–alloc和init原理 我们分析过,iOS环境下,为了容错处理,创建。
在 libmalloc/src/nano_malloc.c 第193行,我们发现如下的代码:
而其中NANO_REGIME_QUANTA_SIZE
为16,
|
这里的意思是,如果size为0,即创建空对象,返回16位默认空间。
而
k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM |
是向右移动4位,即16字节对齐,那究竟是怎么实现的,我们不妨来看下,假定给定申请的内存size 为40,那么
40(size) + 16 - 1 = 55 |
er, 第205行中返回的slot_bytes 即是最终对齐后的内存空间位,即给入40,最终系统对齐后,返回48位(16的整数位)。
4 结论
在iOS环境下,我们创建的对象和指针遵循16位对齐的原则,而其属性位8字节对齐。