iOS内存对齐
一、什么是内存对齐
内存对齐(Memory alignment),也叫字节对齐。
现代计算机中内存空间都是按照 byte
划分的,从理论上讲似乎对任何类型的变量的访问可以从地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
举一个简单的例子, 64
位系统,int
所占内存空间为 4
bytes
,char
为 1
byte
。如果把它们放在一个结构体中,则所占的内存空间应该是 4 + 1 = 5 bytes
。而事实上,在Xcode
环境,sizeof
操作的结果都是 8 bytes
:
二、为什么要进行内存对齐
之所以要内存对齐,有两方面的原因:
平台原因:各个硬件平台
对存储空间
的处理
上有很大的不同。一些平台
对某些特定类型
的数据
只能从某些特定地址
开始存取
。比如,有些架构的CPU
在访问一个没有进行对齐
的变量
的时候会发生错误
,那么在这种架构下编程必须
保证字节对齐
。
性能原因:内存对齐
可以提高
存取效率
。比如,有些平台每次读都是从偶地址开始,如果一个int
型(假设为32
位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit
,而如果存放在奇地址开始的地方,就需要2
个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit
数据。
三、内存对齐的原则
1.数据成员
对齐规则:结构(struct
)(或联合(union
))的数据成员,第一个数据成员放在offset
为0
的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int
在 32
位机为4
字节,则要从4
的整数倍地址开始存储,再比如short
在 32
位机为2
字节,则要从2
的整数倍地址开始存储)。
2.结构体
作为成员
:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素
大小的整数倍
地址开始存储.(struct a
里存有struct b
,b
里有char
,int
,double
等元素,那b
应该从8
的整数倍开始存储.)
3.收尾工作:结构体的总大小,也就是sizeof
的结果,必须是其内部最大成员的整数倍.不足的要补齐。
字节表
四、内存对齐底层探究
首先为什么要探索结构体内存对齐
呢?因为看objc
源码会发现万物皆对象
的基础
是个结构体
,当我们创建对象的时候,不需要去注意属性的先后顺序,因为系统会自动帮我们处理,但是当我们创建结构体的时候就需要我们去分析了,因为系统不会自动给我们优化,
先来看下下面两个结构体(无嵌套)
struct LGStruct1{
double a; // 8
int b; // 4
short c; // 2
char d; // 1
}LGStruct1;
struct LGStruct2{
double a; // 8
char d; // 1
int b; // 4
short c; // 2
}LGStruct2;
我们声明以上两个结构体,两个结构体拥有的数据类型是一摸摸一样样的,理论上内存大小也应该一样(内存是不是真的一样大呢?),然后先看下以下代码
-----16-----24
那么就有问题了,为什么相同的数据类型,里面属性个数也是一样的,但是占用的空间大小不一样呢?这就是今天的重点,结构体内存对齐原则(上面第三点
)
下面就根据内存对齐原则进行简单的计算和分析
LGStruct2内存大小详细过程(min(m,n)
m
表示当前开始的位置,n
表示大小)
变量a
: 占8
个字节,offert
从0
开始, min(0,8)
, 即0 ~ 7
存放a
变量b
: 占4
个字节,offert
从8
开始(12
可以整除4
), min(8,4)
, 即8 ~ 11
存放b
变量c
: 占2
个字节,offert
从12
开始(12
可以整除2
),min(12,2)
,即12 ~ 13
存放c
变量d
: 占1
个字节,offert
从14
开始(14
可以整除1
),min(14,1)
,即14
存放d
\
结果显示 LGStruct1
的实际的内存大小是15
字节,LGStruct1
中最大的变量是a
占个 8
字节。所以LGStruct1
的实际内存大小必须是8的整数倍,15
不是8
的整数倍,向上取整,不足的自动补齐为16
字节。最后LWStruct1
的内存大小为16
字节。
LGStruct1
解析图如下
LGStruct2内存大小详细过程
变量a
: 占8个
字节,offert
从0
开始, min(0,8)
, 即0 ~ 7
存放a
。
变量d
: 占1
个字节,offert
从8
开始(8
可以整除1
), min(8,1)
, 即8
存放d
。
变量b
: 占4
个字节,offert
从9
开始(9
不可以整除4
), min(9,4)
,9 % 4 != 0
,继续往后移动直到找到可以整除4
的位置 12
即12 ~ 15
存放b
。
变量c
: 占2
个字节,offert
从16
开始(16
可以整除2
),min(16,2)
,即16 ~ 17
存放c
。
结果显示 LGStruct2
的实际的内存大小是18
字节,LGStruct2中
最大的变量是a
占个 8
字节。所以LGStruct2
的实际内存大小必须是8
的整数
倍,18
不是8
的整数
倍,向上取整,不足的自动补齐为24
字节。最后LGStruct2
的内存大小为24
字节。
LGStruct2
解析图如下
结构体中嵌套结构体
struct LGStruct1{
double a; // 8
int b; // 4
short c; // 2
char d; // 1
}LGStruct1;
struct LGStruct2{
double a; // 8
char d; // 1
int b; // 4
short c; // 2
}LGStruct2;
struct LGStruct3{
long a; // 8
int b; // 4
short c; // 2
char d; // 1
struct LGStruct2 lwStr;
}LGStruct3;
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"-----%lu-----%lu----%lu",sizeof(LGStruct1),sizeof(LGStruct2),sizeof(LGStruct3));
}
return 0;
}
打印的结果
-----16-----24----40
LGStruct3
内存大小详细过程
变量a
: 占8
个字节,offert
从0
开始, min(0,8)
, 即0 ~ 7
存放a
变量b
: 占4
个字节,offert
从8
开始(8
可以整除4
), min(8,4)
, 即8 ~ 11
存放b
变量c
: 占2
个字节,offert
从12
开始(12
可以整除2
),min(12,2)
,即12 ~ 13
存放c
变量d
: 占1
个字节,offert
从14
开始(14
可以整除1
),min(14,1)
,即14
存放d
\
变量lwStr
:lwStr
是结构体变量,内存对齐原则结构体成员
要从其内部
的最大
元素大小的整数倍
地址开始存储。LGStruct2
中的最大的
变量占8
字节,所以offert
从16
开始,LGStruct2
的内存大小是18
字节。min(16,18)
,即18 ~ 33
存放 lwStr
结果显示 LGStruct3
的实际的内存大小是34
字节,LGStruct3
中最大的变量是lwStr
和 a
都是 8
字节。所以LGStruct3
的实际内存大小必须
是8
的整数倍
,34
不是8
的整数倍,向上取整,不足的自动补齐为40
字节。最后LGStruct3
的内存大小为40
字节。
LGSTruct3解析图如下
五、总结
1、简单的结构体:第⼀个数据成员放在offset为0的地⽅,以后每个数据成员存储的起始位置要从该成员⼤⼩的整数倍 开始存储。 2、有嵌套结构体的结构体:则嵌套的结构体成员要从其内部最⼤元素⼤⼩的整数倍地址开始存储。 3、结构体的总⼤⼩,必须是其内部最⼤成员大小的整数倍,不⾜的要补全。
内存对齐有制定了一套规则,目的是提高cpu
的存取效率
和安全的访问
。字节对齐可能浪费了部分内存,但是同时进行内存优化尽可能的降低了内存的浪费,即保证了存取的速率,又减少了内存的浪费,不得不说真的很优秀啊。
转载自:https://juejin.cn/post/6976886097309761549