在上一篇alloc
方法的探索中,我们提到了内存对齐
的概念,这篇文章我们就主要介绍一下内存对齐
。
结构体的字节对齐
一个例子
我们首先定义几个结构体,初始化,看一下每个结构体的内存大小。
1 | struct JSPerson1 { |
可以看到打印结果为:
1 | struct1的大小==24 |
JSPerson1
和JSPerson2
的属性数量相同,为什么打印的大小结果不一样呢,这里就引入了字节对齐
的概念,我们先看一下OC中各类型占用的内存大小:
结构体对齐原则
- 1、数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储。
- 2、结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
- 3、收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员(基本数据类型)的整数倍.不足的要补⻬。
根据上面的表格和规则我们分析一下例子中三个结构体占用的内存大小(64位系统)
首先看
struct1
1
2
3
4
5
6struct JSPerson1 {
double a;
char b;
int c;
short d;
}struct1;根据规则1,
a
变量存储的空间是[0,7]
,b
变量存储的位置是[8]
,c
因为本身是int
4个字节大小,所以不能从9
的位置开始存储,要从4
整数倍开始存储所以c
存储的空间是[12,15],d
是short
类型大小为2
,16
是2
的整数倍,所以d的存储位置是[16,17]。根据规则3,结构体的总大小需要是内部最大成员的整数倍,所以struct1
的大小为24
.再看
struct2
1
2
3
4
5
6struct JSPerson2 {
double a;
int b;
char c;
short d;
}struct2;和上面一样
a
变量存储的空间是[0,7]
,b
变量存储的位置是[8,11]
,c
变量的存储空间是[12]
,d
变量的存储空间是[14,15]根据规则3
struct2
的大小为16
最后看
struct3
1
2
3
4
5
6
7
8
9struct JSPerson3 {
double a;
int b;
char c;
short d;
int e;
struct JSPerson1 str;
int f;
}struct3;前四个变量和
struct2
类似,a
变量存储的空间是[0,7]
,b
变量存储的位置是[8,11]
,c
变量的存储空间是[12]
,d
变量的存储空间是[14,15],e
变量存储空间是[16,19]
,结构体成员比较特殊我们根据规则2,str
成员的起始地址要是它内部最大成员的大小的整数倍也就是8
,所以str存储的空间是[24,47],f
所占的内存空间是[48,51],注意这个地方有个点:结构体的总大小是其最大基本成员变量的大小的整数倍,所以struct3
的内存大小是56
(8
的整数倍)。
对象的字节对齐
看完了结构体的内存对齐,我们看一下对象的,其实我们很容易可以想到,对象其实也是一个结构体,它和结构体的字节对齐规则也是一样的。我们来验证一下。
1 | @interface Person1 : NSObject{ |
我们定义两个类Person1
和Person2
,分别打印他们实际占用内存和系统分配内存大小:
1 | Person1实际占用: class_getInstanceSize = 24 |
这里有个注意的点就是不要漏掉isa
指针,对象结构体中第一个成员变量就是isa
,所以我们分析的方法和结构体一样,Person1
的isa
存储地址是[0,7]
,_age
的存储空间是[8,11]
,_weight
的存储空间是[12,15]
,_name
的存储空间是[16,23]
,所以Person1
实际空间是24
,同样的方法可得Person2
的实际空间是32
。
这里我们可以看到,成员变量的顺序是会影响到对象实际占用的内存大小,类的属性最后其实也是会生成对应的成员变量,那么属性的定义顺序是不是也会影响对象的实际占用内存大小呢,我们带着这个问题继续探索:
1 | @interface Person1 : NSObject |
我们把成员变量换成属性继续打印上面的内容:
1 | Person1实际占用: class_getInstanceSize = 24 |
发现这次打印Person1
和Person2
实际占用的内存大小是相同的,这是为什么呢?我们将main.m
文件转换成c++
代码看一下:
1 | xcrun -sdk iphonesimulator clang -rewrite-objc main.m |
我们打开main.cpp
文件,搜索Person1
和Person2
:
1 | struct Person1_IMPL { |
看到这里我们明白了,原来编译器帮我们做了一层优化,将属性按照最优的顺序转换成成员变量。所以我们定义属性的顺序并不会影响到对象实际占用的内存大小。
内存对齐的内容就探索到这里,后面会继续探索ios 底层
。