何为内存对齐
内存对齐就是按照成员变量的声明顺序,依次安排内存,其偏移量为成员大小的整数倍,0看做任何成员的整数倍,最后结构体的大小为最大成员的整数倍。
内存对齐是指首地址对齐,而不是说每个变量大小对齐。
为何要有内存对齐
-
平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
-
性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的.它一般会以双字节,四字节,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
开始,结果是不是 0xc00001c0b0
、0xc00001c0b1
、0xc00001c0b3
其实不然,跑出来的结果是
- 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
内存对齐带来的影响
从上面例子中,我们知道,结构体中元素顺序不同,结构体所占内存大小也不同,给元素选择合适的顺序可以节省内存空间
这里建议:把相同类型的元素排到一起