往期精选文章推荐:
前言
在前面的文章 《深入理解 go reflect》和 《深入理解 go unsafe》 中都提到内存对齐,由于篇幅原因没有展开详细介绍,后面有同学私聊我想了解一下内存对齐的细节,今天和大家简单聊聊内存对齐。
为什么要内存对齐
下面是维基百科对内存对齐的描述:
现代计算机CPU一般是以32位元或64位元大小作地址对齐,以32位元架构的计算机举例,每次以连续的4字节为一个区间,第一个字节的位址位在每次CPU抓取资料大小的边界上,除此之外,如果要访问的变量没有对齐,可能会触发总线错误。
总的来说,内存对齐有两点好处:
-
方便CPU读写内存(性能更好)
现代计算机体系结构通常以特定的字节边界来访问内存。如果数据的存储地址没有按照合适的边界对齐,可能会导致多次内存访问,降低程序的执行效率。例如,对于一个 32 位的整数,如果存储在非 4 字节对齐的地址上,可能需要两次内存读取操作才能获取完整的数据,而在对齐的地址上,一次读取操作即可。
-
平台兼容性
不同的硬件平台和操作系统对内存对齐的要求可能不同。通过遵循合适的内存对齐规则,可以确保程序在不同的环境下都能正确运行,提高程序的可移植性。
内存对齐规则
内存对齐是一种将数据在内存中进行排列的方式,目的是提高内存访问的效率和保证数据的完整性。数据的内存排列方式最直观的体现就是数据的内存地址,说白了就是数据的内存地址要符合一定规则,以便于CPU读取,这个规则就是下面要讲的内存对齐规则。
默认对齐值
在不同的平台上的编译器有自己默认的"对齐值", 可以通过预编指令 #pragmapack(n) 进行变更,n 就是代指"对齐系数"。一般来讲,常用的平台对齐系数是: 32位是 4,64位是 8。
基本类型的对齐
在 Go 语言中,基本数据类型通常按默认对齐值和数据自身大小的最小值进行对齐,也就是说数据的内存地址必须是默认对齐值和数据大小的最小值的整数倍。
使用伪代码表示一下:
// 64 位平台,默认对齐值 8
const defaultAlign int = 8
func GetAlign(a any) int {
return min(defaultAlign, sizeOf(a))
}
常见的基本类型对齐值:
数据类型 | 自身大小 | 32位平台对齐值 | 64位平台对齐值 |
---|---|---|---|
int、uint | 4 or 8 | 4 | 8 |
int32、uint32 | 4 | 4 | 4 |
int64、uint64 | 8 | 4 | 8 |
int8、uint8 | 1 | 1 | 1 |
int16、uint16 | 2 | 2 | 2 |
float32 | 4 | 4 | 4 |
float64 | 8 | 4 | 8 |
bool | 1 | 1 | 1 |
基本类型的对齐示例:
package main
import