内存对齐详解

2021-03-31

内存对齐详解

一、内存对齐的概念

在计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,从结构体存储的首地址开始,每个元素放置到内存中时,它都会认为内存是按照自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始,这就是内存对齐。

二、内存对齐的原因

1、平台原因(移植原因):不是所有的硬件平台都能访问地址上的任意数据的;某些硬件平台只能在某些地址处取某些类型的数据,否则抛出硬件异常。

2、性能原因:数据结构(尤其是栈)应该尽可能地在边界上对齐。原因在于,访问未对齐的内存,处理器需要作两次内存访问,然后再合并两次访问的结果;而对齐的内存访问仅需要一次访问。

三、对齐规则

每个特定平台上的编译器都有自己的默认“对齐系数”(对齐模数)k。通常k = 1,2,4,6,8,16来改变这一系数。

1.基本类型的对齐值就是MemoryLayout<T>.sizesizeof的大小;

2.数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照k指定的数值和这个数据成员自身长度中比较小的那个进行;

3.结构整体对齐规则:再数据成员完成各自对齐之后,结构本身也要进行对齐,对齐将按照k指定的数值和结构最大数据成员长度中比较小的那个进行;

总结:对齐原则是任何 K 字节的基本对象的地址必须是 K 的倍数

四、基本数据类型MemoryLayout

值类型

1
2
3
MemoryLayout<Int>.size           //8
MemoryLayout<Int>.alignment //8
MemoryLayout<Int>.stride //8

引用类型

1
2
3
MemoryLayout<T>.size           //8
MemoryLayout<T>.alignment //8
MemoryLayout<T>.stride //8

指针类型

1
2
3
4
5
6
7
MemoryLayout<unsafeMutablePointer<T>>.size           //8
MemoryLayout<unsafeMutablePointer<T>>.alignment //8
MemoryLayout<unsafeMutablePointer<T>>.stride //8

MemoryLayout<unsafeMutableBufferPointer<T>>.size //16
MemoryLayout<unsafeMutableBufferPointer<T>>.alignment //16
MemoryLayout<unsafeMutableBufferPointer<T>>.stride //16

还有可选类型,占一个字节。

我们来看看,在Swift中,struct是值类型,一个没有引用类型的Struct临时变量都是在栈上存储的。

1
2
3
4
struct Test {
var b: Double = 0
var a: Double = 0
}

结果

1
2
3
print(MemoryLayout<Test>.size) // 16
print(MemoryLayout<Test>.stride) // 16
print(MemoryLayout<Test>.alignment) // 8

分析: a、b都是Double类型,字节为8字节。b从地址0开始,占用0到7号地址,a从8号开始占用8到15地址。所以size:16,stride:16,aglinment: 8。

在看看这样

1
2
3
4
struct Test {
var b: Double? = 0
var a: Double = 0
}

结果

1
2
3
print(MemoryLayout<Test>.size) // 24
print(MemoryLayout<Test>.stride) // 24
print(MemoryLayout<Test>.alignment) // 8

分析:b是Double类型,且是可选的,a是Double类型。b占9个字节,a占8个字节。根据内存对齐原则:b占0到7号,可选类型:8到15地址号。a占16到23号,总共24字节。可见,Test增加了8个字节。实际上,可选类型只增加一个类型。

1
2
print(MemoryLayout<Double>.size) // 8
print(MemoryLayout<Optional<Double>>.size) // 9

结论:Swift的可选类型是非常浪费内存空间的。

再继续看,

1
2
3
4
struct Test {
var a: Double
var b: Bool
}

结果

1
2
3
print(MemoryLayout<Test>.size) // 9
print(MemoryLayout<Test>.stride) // 16
print(MemoryLayout<Test>.alignment) // 8

分析:a是Double类型,8个字节大小,占0到7地址号,b是bool类型,1个字节大小,占8地址号。总共大小:9字节大小

然后,我们再看看,

1
2
3
4
struct Test {
var b: Bool
var a: Double
}

结果

1
2
3
print(MemoryLayout<Test>.size) // 16
print(MemoryLayout<Test>.stride) // 16
print(MemoryLayout<Test>.alignment) // 8

分析:b是bool类型,1个字节大小,占0到7地址号,a是Double类型,8字节大小,占8到15地址号。所以Test大小:16字节。

结论:内存大小与成员位置顺序有关。