什么是字节对齐
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
作用和原因
简单来讲是由CPU架构造成的。比如arm64(ARM)
,x86(Intel、AMD)
, PowerPC(IBM)
等。
作为iOS Developer
,常见的有armv7
、armv7s
、arm64e
、arm64
、i386
、x86_64
。
CPU为了高效执行指令或者读取数据,不是按照字节读取的,而是根据数据将内存分块,每块的字节数都是偶数,如 2
、4
、8
、16
字节。每次读取都是一个固定的开销,减少内存存取次数将提升程序的性能。
- 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 硬件原因:经过内存对齐之后,CPU的内存访问速度大大提升。
iOS的内存对齐
每个特定的平台上的编译器都有自己的默认对齐系数
(也叫对齐模数
)。我们可以通过预编译命令#pragma pack(n),n=1,2,4,8,16
来改变这一系数,其中的n就是要指定的对齐系数
。Xcode的对齐系数就是8
,GCC对齐系数是4
。
对齐原则
我们在了解内存对齐之前,先看看内存对齐的原则:
- 数据成员对齐规则:
Struct 或 union 的数据成员第一个数据成员放在偏移为0的位置,以后每个成员的偏移为 min(对齐系数,自身长度)的整数倍,不够整数倍的补齐。 - 数据成员为结构体:
该数据成员的自身长度为其最大长度的整数倍开始存储 - 整体对齐规则:
数据成员按照上述规则对齐之后,其本身也要对齐,对齐原则是min(对其系数,成员最大长度)的整数倍。
翻译一下就是
- 前面的地址必须是后面的地址整数倍,不是就补齐
- 结构体里面的嵌套结构体大小要是该嵌套结构体最大元素大小的整数倍
- 整个Struct的地址必须是最大字节的整数倍
代码求源
接下来,需要用代码验证、查看对象alloc
时开辟的真实空间具体是多少。
可以用class_getInstanceSize(class)
、malloc_size()
来打印出对象的大小。
struct StructOne {
char a; //1字节
double b; //8字节
int c; //4字节
short d; //2字节
} MyStruct1;
struct StructTwo {
double b; //8字节
char a; //1字节
short d; //2字节
int c; //4字节
} MyStruct2;
NSLog(@"%lu---%lu", sizeof(MyStruct1), sizeof(MyStruct2));
得到的结果是24---16
。
StructOne
中
a
为1字节,不够8个字节,而第二个成员为b为double
类型,为8个字节,所以b
可以被编译器单独读取,为了读取效率,不可以分割也不能和其他数据合并,所以要对a
进行7个字节补充,使其占用8个字节。c
为int类型,占用4个字节,d
为short类型,占用2个字节,c和d可以合并另外再对其进行2个字节的补充,使其占用8个字节。 这样就符合了内存对其原则,最后StructOne
占用的空间为(1+7)+8+(4+2+2)=24。
StructTwo
中
- b占用8个字节
- a、d、c可以合并,再补充1个字节,使其占用8个字节
最后
StructTwo
占用的空间为8+(1+2+4+1)=16。
总结
字节对齐是为了提升CPU执行效率产生的规则,原理是按照对齐系数对内存进行排列读取。在iOS中编译器帮我们处理了这一问题,所以无需考虑,但在日常开发中如果在写C语言的代码时,就可以利用字节对齐的规则进行生命变量,以提升效率。