memory alignment

2022/02/20

何为内存对齐

内存对齐就是按照成员变量的声明顺序,依次安排内存,其偏移量为成员大小的整数倍,0看做任何成员的整数倍,最后结构体的大小为最大成员的整数倍。

内存对齐是指首地址对齐,而不是说每个变量大小对齐。

为何要有内存对齐

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

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

尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的.它一般会以双字节,四字节,8字节,16字节甚至32字节为单位来存取内存,我们将上述这些存取单位称为内存存取粒度.

现在考虑4字节存取粒度的处理器取int类型变量(32位系统),该处理器只能从地址为4的倍数的内存开始读取数据。

假如没有内存对齐机制,数据可以任意存放,现在一个int变量存放在从地址1开始的联系四个字节地址中,该处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器.这需要做很多工作

内存对齐规则

由于内存对齐的原因,结构体实际占用字节数永远大于等于结构体所有字段字节数之和。没错,确实有正好相等的情况,后面我们会看到在一种什么样的机缘巧合之下他们会恰好相等。

对于结构体的每个字段,我们先来认识一下如下4个概念:

  • 对齐宽度
  • 本身所占字节数
  • 实际占用字节数
  • 偏移量

其中 : 对齐宽度 ≤ 本身所占字节数 ≤ 实际占用字节数。

首先来说,本身所占字节数就是类型大小。当把类型放到结构体时,它实际占用字节数是大于等于类型本身大小的,多出来的部分叫填充字节。也就是说实际占用字节数=本身所占字节数+填充字节数。

对齐宽度是类型的一种属性,他和类型本身以及操作系统有关。一般情况下,对齐宽度和类型大小是一致的。比如byte和bool类型的对齐宽度是1字节,int32类型对齐宽度是4字节。那为什么对齐宽度又会小于类型大小呢?那是因为对齐宽度有一个上限,在32位系统上,对齐宽度最大为4字节,因此,即便是int64类型,对齐宽度也是4字节,而不是8字节;相应的,在64位系统上,对齐宽度为8字节,即使是string(本身占16字节),对齐宽度也只有8字节

总的来说,有两条规则:

地址要对齐

字段的地址偏移要是自身长度的整数倍

举个例子:

package main

import (
	"fmt"
)

type Data struct {
	A byte
	B int16
	C int64
}

func main() {
	data := Data{}
	fmt.Printf("A:%p\nB:%p\nC:%p\n", &data.A, &data.B, &data.C)
}

猜下打印结果中A、B、C的内存地址应分别是什么?

我们知道,结构体中A、B、C分别占1、2、8个字节

假设内存地址从0xc00001c0b0开始,结果是不是 0xc00001c0b00xc00001c0b10xc00001c0b3

其实不然,跑出来的结果是

  • A:0xc00001c0b0
  • B:0xc00001c0b2 (不应该是 0xc00001c0b1吗,A只占一个字节)
  • C:0xc00001c0b8 (不应该是 0xc00001c0b3吗,AB共占三个字节)

这就和上面的规则有关系了,字段的地址偏移是自身长度的整数倍

A是第一个元素,从0开始,没问题;

接下来是B占了两个字节,按照规则,该字段的地址偏移应该是2的整数倍,A之后的起始地址是1,不是2的整数倍,只能是接下来的2;

同理,C占8个字节,偏移地址是8的整数倍,也不能是B之后开始的3,只能往后移到8的整数倍8

长度要对齐

结构体的长度要至少是内部最长的基础字段的整数倍

举例:

package main

import (
	"fmt"
	"unsafe"
)

type Data struct {
	A byte
	C int64
	B int16

}

func main() {
	data := Data{}
	fmt.Printf("sizeOf:%d\n", unsafe.Sizeof(data))
}

还是上面的结构体,猜一下,打印结果应该是多少呢?

显然不是1+2+8=11,我们知道C开始的地址是8,相当于A、B共占了8个字节,C占了8个字节,一共就是8+8=16了

那如果把结构体中的元素调下顺序呢?

type Data struct {
	A byte
	C int64
	B int16
}

根据上面规则,A虽然大小只有1个字节,但是由于偏移地址是自身长度的整数倍,C的起始地址是8,A实际占了8个字节,那结果是不是 8+8+2=18呢?

其实也不然,实际整个结构体占了24个字节,这就涉及到第二条规则了,结构体的长度要至少是内部最长的基础字段的整数倍。

结构体中,最长基础字段是C占了8个字节,由于偏移对齐,A占了8个字节 然后C占了8个字节,虽然B只有两个字节大小,但总长度是最长也就是8的整数倍,就不能是18了,后面需要补齐6个字节,总长度就是8*3=24了

那如果A、B调换下顺序呢,结果也是24

内存对齐带来的影响

从上面例子中,我们知道,结构体中元素顺序不同,结构体所占内存大小也不同,给元素选择合适的顺序可以节省内存空间

这里建议:把相同类型的元素排到一起

Post Directory