导读
空结构体
var a struct{}
fmt.Println(unsafe.Sizeof(a)) //0
很奇怪,空指针按常理来说怎么都是个整数类型的指针,怎么会不占用空间呢?更奇怪的是,我们用dlv工具也打不上断点(var a struct{}),go tool compile -S main.go | grep "main.go:" 发现根本没有这行代码对应的汇编,直接被优化掉了(没有main.go:8)
image.png
仔细想想也是,既然不占用内存直接连寄存器存值都没必要
我们去之前分配内存的函数mallocgc看一下,发现这么一行代码
image.png
如果size=0那么就返回一个zerobase的地址
// base address for all 0-byte allocations
var zerobase uintptr
看这变量的注解恍然大悟,所有的零比特(这里空结构体)内存都是指向这个地址。
那么我们基于内存零开销就能做很多事情了,比如当作信号传入channel,与map结合实现set即值没有意义,只需要键
继承?嵌入字段!
go没有继承的概念,把结构体a当作结构体b成员,结构体b也能调用a的方法,甚至只需要提供类型就可以(这叫做嵌入字段)
type Person struct{}
func (p Person) say() {
fmt.Println("hi")
}
type Book struct{
Person
}
func main() {
var a = Book{}
a.say() // "hi"
}
内存布局
内存大小与数组一样仅仅是简单的成员大小相加?不是的,但是结构体的内存是非常紧凑的,我们要知道一个前提,计算机读取数据是以字长为单位的(我以8字节的字长系统为例),那么我们就要确保我们要读取的变量刚好在这个字长的整数倍范围内,不能东一块西一块,不然不好拼接,以结构体T为例
type T struct{
a int8
b string //16
}
如果我们是紧挨着排列会怎么样
第一次按字长读取a能读到,b不完整只读取了一部分,那剩下的部分要占1+1/8个字长,读完后还要切分拼接那效率多低(跨字长的排布会影响内存原子性和效率),我们需要把b的开头移到下一个字长,读b的时候连续读取2个字长不就效率更高了。其实a,b在字长这个区间上到底占哪一个是受对齐系数影响的
unsafe.Alignof()返回一个整数
变量的内存地址必须被对齐系数整除,string的对齐系数为8,必须在8的倍数上,这是为了内存对齐,那结构体的成员顺序不就影响了内存大小(结构体的对齐系数为成员最大的对齐系数)
空结构体成员怎么办?
对齐系数是1,意味着可以随便放?
不是的,在结构体中意味着占位,大小会与前一个相同(前提是不是最后一个)
那放在最后呢
type A struct{
a int8
b struct{}
c string
}
type B struct {
a int8
b string
c struct{}
}
func main() {
var a A
var b B
fmt.Println(unsafe.Sizeof(a),unsafe.Alignof(a))
fmt.Println(unsafe.Sizeof(b),unsafe.Alignof(b))
}
24 8
32 8
占了32个字节,意味着最后的空结构体要占位,占满一个字长
interface
感觉没啥好讲的,跳过了
小结
所以在结构体中一定要注意字段的顺序,降低对空间的占用
网友评论