探索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.png 看到结果是
19
,说明这里存的是height
。 -
第
4
个8
字节:同样打印一下image.png 结果是
Mlqq
,说明这个字节存的是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字节的属性,属性字节数都相等,声明在后的属性排在前面
- 不发生重排的属性按声明的顺序排列
网友评论