探索OC类的大小要先来说一下结构体,因为OC类底层都是以结构体的形式存在的。
1 结构体大小-字节对齐
我们先定义一下两个结构体:
//1、定义两个结构体
struct Mystruct1{
char a; //1字节
double b; //8字节
int c; //4字节
short d; //2字节
}Mystruct1;
struct Mystruct2{
double b; //8字节
int c; //4字节
short d; //2字节
char a; //1字节
}Mystruct2;
//计算 结构体占用的内存大小
NSLog(@"%lu-%lu",sizeof(Mystruct1),sizeof(Mystruct2));
一下是输出结果:

字节对齐导致的。
内存对齐规则
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。在ios中,Xcode默认为#pragma pack(8),即8字节对齐
(1)数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第
一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要
从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,
结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存
储。min(当前开始的位置mn)m=9 n=4 9 10 11 12
(2)结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从
其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b
里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
(3)收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补⻬。
验证对齐规则
下表是各种数据类型在ios中的占用内存大小,根据对应类型来计算结构体中内存大小

Mystruct1和Mystruct2两个结构体大小不一样根据内存对齐规则计算
MyStruct1的内存大小,详解过程如下:
- 变量
a:占1字节,从offset为0开始,0是1的整数倍,所以0存储a - 变量
b:占8字节,应该从offset为1开始存,但是1不是8的整数倍,所要往后移动到8的倍数,即从offset为8开始,8,9,10,11,12,13,14,15位置存放b - 变量
c:占4字节,从16开始,16是4的倍数,所以16,17,18,19位置存放c - 变量
d:占2字节,从20开始,20是2的倍数,所以20,21位置存放d -
Mystruct1大小应该满足是其最大成员变量b所占字节数8的整数倍,所以Mystruct1的大小为24
根据内存对齐规则计算MyStruct2的内存大小,详解过程如下:
- 变量
b:占8字节,从offset为0开始,0-7存储b - 变量
c:占4字节,从8开始,8是4的倍数,所以8-11存储c - 变量
d:占2字节,从12开始,12是2的倍数,所以12-13存储d - 变量
a:占1字节,从14开始,14是1的倍数,所以14存储a -
Mystruct2大小应该满足是其最大成员变量b所占字节数8的整数倍,所以Mystruct2的大小为16
结构体套嵌结构体
我们在定义一个结构体3如下:
struct Mystruct3 {
int c;
struct Mystruct1 str;
short d;
}Mystruct3;
我们来分析一下Mystruct3的大小:
- 变量
c:占4位,0-3存储 - 结构体
str:根据规则2,开始位置应该为Mystruct1内最大变量的整数倍,Mystruct1最大变量为8,所以开始位置为8的整数倍即位置8,即是位置8存储Mystruct1里的变量a,接下来该存储Mystruct1里的变量b,存储变量b的位置也要是b大小的整数倍,所以从位置16开始,16-23存储变量b,同理24-27存储变量Mystruct1里的变量c,28-29存储Mystruct1里的变量d。因为变量str也是结构体,所以也要满足字体对齐的原则,其大小也要是其内部最大变量b大小8的整数倍,因此要扩展为32。因此8-32存储变量str。 - 变量
d:占2位,33-34存储 -
Mystruct3的大小为其内部最大成员变量的整数倍,Mystruct3最大的成员变量是其结构体成员变量Mystruct1里面的变量b,所以Mystruct3大小为8的倍数40

2 OC类的字节对齐
在alloc&init&new流程提到过alloc流程里面的三个重要的放方法: size = cls->instanceSize(extraBytes);、obj = (id)calloc(1, size);和obj->initInstanceIsa(cls, hasCxxDtor);,这里我们分析size = cls->instanceSize(extraBytes);这个方法。
从这个方法跟进去就是:



static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
这就是16字节对齐,也就是说OC类大小开辟的空间大小是16的倍数。这里就不验证了,感兴趣的朋友可以自己验证一下。
3 OC内存重排
我们先创建一个类MlqqObject,属性参考图中,创建是一个实力对象objc,然后对属性复制,然后打印objc的内存分布,如下图:

第
1个8字节:这个字节里面存的是对象的isa,关于这部分知识在另外一篇文章详细介绍,这里就简单说明一下。-
第
2个8字节:我们打印一下发现是一个不认识的数字image.png -
第
3个8字节:同样打印一下看到结果是image.png19,说明这里存的是height。 -
第
4个8字节:同样打印一下结果是image.pngMlqq,说明这个字节存的是name -
第
5个8字节:同样打印一下结果是image.png北京,说明这个字节存的是address
第6以后的字节都打印一下,都没有看到age和weight的值。其实age和weight的值就是在第2个8字节,我么把它拆为两部分分别打印:

正是age和weight的值,这就是系统对属性重排的结果。age和weight都是int类型,占4字节,两个int是8字节,正好可以用一个8字节来存存储,就没必要一个int也要占8字节。
height也是int类型,也是占4字节,为什么不是age和height放在一起呢,我们改变一下属性声明的顺序

改变属性声明的顺序后,内存分布的顺序也改变了,现在第2个8字节存的是weight和height,第3个8字节存的是age,后面才是NSString类型。
另外同样是第2个8字节,也就是重排的字节,声明在前面的内存中在后面。weight声明在height前面,内存0x0000001300000011,前4个字节是height,后4个字节是weight,为了更好的说明,我们再增加多一点属性,按照下面方式:

- 第
2个8字节:0x6867666564636261从后往前代表的分别是a、b、c、d、e、f、g、h,的ascii值,跟声明的顺序是相反的。 - 第
3个8字节:0x0000001100006a69,前4个字节0x00000011的值是17,代表weight。后4个字节0x006b6a69,从后往前代表的是i、j、k,这一个里面存了3个长度的属性分别是int占4字节,short占2字节,char占1字节,排列的顺序是先排多字节的属性,后排小字节属性。 - 第
4个8字节:0x0000001200000013,前4个字节0x00000012的值是18,代表age,后4个字节0x00000013的值是19,代表height跟声明的顺序是相反的。
内存重排总结:
- 当属性中有属性所占字节数小于8时,需要重排
- 重排时属性占字节数小的属性排在前面,满足字节数之和为8字节的属性排在一起
- 重排在同一个8字节的属性,属性字节数不相等,字节数大的排在前面,字节数小的排在后面
- 重排在同一个8字节的属性,属性字节数都相等,声明在后的属性排在前面
- 不发生重排的属性按声明的顺序排列



