先看一个结构体
// 写法一
type T1 struct {
a int8
b int64
c int16
}
// 写法二
type T2 struct {
a int8
c int16
b int64
}
对于这两个结构体,都有a、b、c三个定义完全一样的字段,只是在定义结构体的时候字段顺序不一样而已,那么两种写法有什么影响吗?
对于新手来说,感觉着没有什么区别的,只是一个书写顺序不同而已,但对于go编译器来说,则有着很大的区别,特别是在不同架构上(32位/64位)的编译器,在一定程度上对内存的使用大小和执行效率有着一定的不同。这里的主要知识点就是golang语言中的内存对齐概念(alignment guarantee),https://gfw.go101.org/article/memory-layout.html
类型的尺寸和结构体字节填充(structure padding)
Go白皮书只对以下种类的类型的尺寸进行了明确规定。
类型种类 尺寸(字节数)
------ ------
byte, uint8, int8 1
uint16, int16 2
uint32, int32, float32 4
uint64, int64 8
float64, complex64 8
complex128 16
uint, int 取决于编译器实现。通常在
32位架构上为4,在64位
架构上为8。
uintptr 取决于编译器实现。但必须
能够存下任一个内存地址。
Go白皮书没有对其它种类的类型的尺寸最初明确规定。 请阅读值复制成本一文来获取标准编译器使用的各种其它类型的尺寸。
标准编译器(和gccgo编译器)将确保一个类型的尺寸为此类型的对齐保证的倍数。
为了满足上一节中规定的地址对齐保证要求,Go编译器可能会在结构体的相邻字段之间填充一些字节。 这使得一个结构体类型的尺寸并非等于它的各个字段类型尺寸的简单相加之和。
下面是一个展示了一些字节是如何填充到一个结构体中的例子。 首先,从上面的描述中,我们已得知(对于标准编译器来说):
- 内置类型
int8
的对齐保证和尺寸均为1个字节; 内置类型int16
的对齐保证和尺寸均为2个字节; 内置类型int64
的尺寸为8个字节,但它的对齐保证在32位架构上为4个字节,在64位架构上为8个字节。 - 下例中的类型
T1
和T2
的对齐保证均为它们的各个字段的最大对齐保证。 所以它们的对齐保证和内置类型int64
相同,即在32位架构上为4个字节,在64位架构上为8个字节。 - 类型
T1
和T2
尺寸需为它们的对齐保证的倍数,即在32位架构上为4n个字节,在64位架构上为8n个字节。
type T1 struct {
a int8
// 在64位架构上,为了让下一个字段b的地址为8字节对齐,
// 需在在字段a这里填充7个字节。在32位架构上,为了让
// 字段b的地址为4字节对齐,需在这里填充3个字节。
b int64
c int16
// 为了让类型T1的尺寸为T1的对齐保证的倍数,
// 在64位架构上需在这里填充6个字节,在32架构
// 上需在这里填充2个字节。
}
// 类型T1的尺寸在64位架构上位24个字节(1+7+8+2+6),
// 在32位架构上为16个字节(1+3+8+2+2)。
// 以保存每个字段都是8(64位架构)或者4(32位架构)的的整数倍
type T2 struct {
a int8
// 为了让下一个字段c的地址为2字节对齐,
// 需在字段a这里填充1个字节。
c int16
// 在64位架构上,为了让下一个字段b的地址为8字节对齐,
// 需在字段c这里填充4个字节。在32位架构上,不需填充
// 字节即可保证字段b的地址为4字节对齐的。
b int64
}
// 类型T2的尺寸在64位架构上位16个字节(1+1+2+4+8),
// 在32位架构上为12个字节(1+1+2+8)。
从这个例子可以看出,尽管类型T1
和T2
拥有相同的字段集,但是它们的尺寸并不相等。每个字段的大小都要受下一个字段大小的影响,以方便下个字段对齐。所以建议在开发中,字段占用空间小的放在前面。
一个有趣的事实是有时候一个结构体类型中零尺寸类型的字段可能会影响到此结构体类型的尺寸。 请阅读此问答获取详情。
主要记住使用时,Type size的值越小越好,这样就可以对结构体进行一些内存大小优化了。
有疑问加站长微信联系(非本文作者)