一、什么是内存对齐
内存对齐(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的存取效率和安全的访问。字节对齐可能浪费了部分内存,但是同时进行内存优化尽可能的降低了内存的浪费,即保证了存取的速率,又减少了内存的浪费,不得不说真的很优秀啊。























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)