结构体内存对齐
先来看一个示例
1 | package main |
输出
1 | struct1 大小 24 对齐系数 8 |
内存布局示例图:
先说下为什么要内存对齐
许多计算机系统对基本数据类型的合法地址做出了一些限制,要求某种类型对象的地址必须是某个值 K(通常是 2、4 或 8)的倍数。这种对齐限制简化了形成处理器和内存系统之间接口的硬件设计。例如,假设一个处理器总是从内存中取 8 个字节,则地址必须为 8 的倍数。如果我们能保证将所有的 int64 类型数据的地址对齐成 8 的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个 8 字节内存块中。
无论数据是否对齐,X86-64 硬件都能正确工作。不过,Intel 还是建议要对齐数据以提高内存系统的性能。
上面这段来自于 csapp 3.9.3 章节 ,通俗点说就是 cpu 读取一般是读取固定位数,CPU访问非对齐的内存时为何需要多次读取。
go 的内存对齐
For the numeric types, the following sizes are guaranteed:
1 | type size in bytes |
The following minimal alignment properties are guaranteed:
- For a variable x of any type: unsafe.Alignof(x) is at least 1.
- For a variable x of struct type: unsafe.Alignof(x) is the largest of all the values unsafe.Alignof(x.f) for each field f of x, but at least 1.
- For a variable x of array type: unsafe.Alignof(x) is the same as the alignment of a variable of the array’s element type.
A struct or array type has size zero if it contains no fields (or elements, respectively) that have a size greater than zero. Two distinct zero-size variables may have the same address in memory.
对于数值类型,保证以下大小:
1 | 类型 大小(字节) |
保证以下最小对齐属性:
- 对于任何类型的变量 x:unsafe.Alignof(x) 至少为 1。
- 对于结构体类型的变量 x:unsafe.Alignof(x) 是 x 中每个字段 f 的 unsafe.Alignof(x.f) 的最大值,但至少为 1。
- 对于数组类型的变量 x:unsafe.Alignof(x) 与数组元素类型的变量的对齐方式相同。
如果一个结构体或数组类型不包含大小大于零的字段(或元素),则其大小为零。两个不同的零大小变量在内存中可能有相同的地址。
为什么上面的结构体布局是这样
对于 struct1 来说 b 的对齐系数是 8 所以不能仅跟着 a 而是需要位于第 8 字节处 然后 c 位于第17字节处 struct1 的整体对齐系数是 8 所以需要在 struct1 后面填充 7 字节 使其满足 struct1 的对齐系数。
这里不知道大家会不会有疑问,反正本人是有,为什么 struct1 的后面要填充 7 个字节 。答案很简单 看下面这个操作你就明白了
1 | var arr []struct1 |
如果 struct1 的后面不填充 7 个字节则无法保证 arr 里面结构体的内存对齐。 所以要要求结构体的大小是其偏移量的倍数。
疑问的解决来自于 stackoverflow
知道这个有什么作用
就像实例中看见的一样 同样是两个bool 一个 int64 字段的结构体,占用的内存大小却不一样,所以如果了解这个,写代码时你就可以节省内存。
也有一些开源工具是做这个的。
如structslop 但是我记得我还见过别的 一时没找见。
一个注意点。
当 struct{} 作为结构体最后一个字段时,需要内存对齐。因为如果有指针指向该字段, 返回的地址将在结构体之外,如果此指针一直存活不释放对应的内存,就会有内存泄露的问题(该内存不因结构体释放而释放)。
参考