什么是内存对齐
以一个例子来说明,以64位系统为例
type test struct {
a int32
b byte
}
func main() {
fmt.Println(unsafe.Sizeof(test{})) // 8
}
理论上int32占4个字节,byte占一个字节,test结构体应该占5个字节才对。但实际上占了8个字节,这就是进行了字节对齐。
为什么会出现字节对齐
现代计算机中内存空间都是按照 byte 划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际的RAM内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这和RAM的存储原理有关。感兴趣可以去看这个视频。
尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的。它一般会以2字节,4字节(32位),8字节(64位),16字节甚至32字节为单位来存取内存,我们将上述这些存取单位称为内存存取粒度.
内存存取粒度又称 对齐粒度(也叫对齐模数) 或称 寄存器宽度 或称 机器字长 或称 总线宽度 。gcc中默认#pragma pack(4),可以通过预编译命令#pragma pack(n),n = 1,2,4,8,16来改变这一系数。
假如没有内存对齐机制,数据可以任意存放,现在一个int变量存放在从地址1开始的连续的4个字节地址中,该处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器。这会多做很多工作。所以就出现了对齐机制。
建议看完上面的视频再来看下面的例子。
对齐规则
go语言中对齐规则和c语言的一样。 下面所有的描述都是对64位来说。
-
对于简单类型,如果该类型大小超过8个字节,用8个字节对齐;小于8个字节按该类型的大小对齐,即按小的对齐
-
对于结构体类型,它的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个,这样在成员是复杂类型时,可以最小化长度。
-
对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐。
-
对于数组,比如char a[1024];它的对齐方式和分别写1024个char 是一样的。虽然他的占用大小超过了8,但他的对齐值仍然是1。
对于32位机器来说,机器字长是4;对于128位或更高的机器来说,可能是16,32,64...。
几个例子
- 顺序不同,最后的占用大小不同
type User struct {
a uint8
b uint64
c uint16
d uint32
}
type User2 struct {
a uint8
c uint16
d uint32
b uint64
}
func main() {
fmt.Println(unsafe.Sizeof(User{})) // 24
fmt.Println(unsafe.Sizeof(User2{})) // 16
}
- 结构体中包含结构体 的时候的对齐案例
type User struct {
a uint8 //按1对齐 0%1=0 从0开始到1 占1个字节
b uint32 //按4对齐 4%4=0 从4开始到8 占4个字节
c uint16 //按2对齐 8%2=0 从8开始到10 占2个字节
//User 按最大的元素大小对齐,即uint32=4
//User到10,10不是4的倍数,往后扩充,所以User占12,对齐值是4
}
type User2 struct {
a uint8 // 按1对齐 0%1=0 0-1 占1 注意是:[0, 1)下同
c uint16 // 按2对齐 2%2=0 2-4 占2 [2, 4)
//User:占12个字节,对齐值为4
u User // 按4对齐 4%4=0 4-16 占12
d uint32 // 按4对齐 16%4==0 16-20 占4
b uint64 // 按8对齐 24%4==0 24-32 占8
//User2 按最大的元素大小对齐,即uint64=8
//User2到32 32是8的倍数,所以User2占32,对齐值是8
}
func main() {
fmt.Println(unsafe.Sizeof(User{})) // 12
fmt.Println(unsafe.Sizeof(User2{})) // 32
}
- 结构体中包含数组
type two struct {
s [100]byte // 按byte=1对齐,
//two按最大的元素大小 byte=1 对齐,
//two到100 100是1的倍数,所以two占100,对齐值是1
}
type User2 struct {
a uint8 // 按1对齐 0%1=0 0-1 占1
c uint16 // 按2对齐 2%2=0 2-4 占2
//two:占100个字节,但是按元素中最大的对齐(按1对齐),
s two // 按1对齐 4%1=0 4-104 占100
d uint32 // 按4对齐 104%4=0 104-108 占4
b uint64 // 按8对齐 108不行,往后找 112%4=0 112-120 占8
//User2按最大的元素大小 8 对齐,
//User2到120 120正好是8的倍数,所以User2占120
}
func main() {
fmt.Println(unsafe.Sizeof(two{})) // 100
fmt.Println(unsafe.Sizeof(User2{})) // 120
}
- 结构体包含的结构体中包含数组和其他元素
type two struct {
s [101]byte // 按byte=1对齐,0-101
b uint64 // 按8对齐 104%8=0 104-112 占8
//two按最大的元素大小 8 对齐,
//two到112 112是8的倍数,所以two占112,对齐值为8
}
type User2 struct {
a uint8 // 按1对齐 0%1=0 0-1 占1
c uint16 // 按2对齐 2%2=0 2-4 占2
//two:占112个字节,但是按元素中最大的对齐(按8对齐),
s two // 按8对齐 8%8=0 8-120 占112
d uint32 // 按4对齐 120%4=0 120-124 占4
b uint64 // 按8对齐 128%8=0 128-136 占8
//User2按最大的元素大小 8 对齐,
//User2到136 136正好是8的倍数,所以User2占136
}
func main() {
fmt.Println(unsafe.Sizeof(two{})) // 112
fmt.Println(unsafe.Sizeof(User2{}))// 136
}
推荐阅读