在 go语言中,正常的 struct 一定是需要占用一块内存的,但是有一种特殊情况,如果是一个空struct,那么它的大小为0. 这是怎么回事,空struct 又有什么用呢?
This article is first published in the medium MPP plan. If you are a medium user, please follow me in medium. Thank you very much.
|
|
Empty Struct 的 秘密
特殊变量:zerobase
空结构体是没有内存大小的结构体。这句话是没有错的,但是更准确的来说,其实是有一个特殊起点的,那就是 zerobase
变量,这是一个 uintptr
全局变量,占用 8 个字节。当在任何地方定义无数个 struct {}
类型的变量,编译器都只是把这个 zerobase
变量的地址给出去。换句话说,在 golang 里面,涉及到所有内存 size 为 0 的内存分配,那么就是用的同一个地址 &zerobase
。
例如
|
|
空结构体的变量的内存地址都是一样的。这是因为编译器在编译期间,遇到 struct {}
这种特殊类型的内存分配,会给他分配&zerobase
,这个代码逻辑是在 mallocgc
函数里面:
|
|
这就是Empty struct
的秘密有了这个特殊的 变量,我们利用它可以完成很多功能。
Empty struct 与内存对其
一般情况下,struct 中包含 empty struct ,这个字段是不占用内存空间的,但是有一种情况是特殊的,那就是 empty struct 位于最后一位,它会触发内存对齐 。
比如
|
|
因为如果有指针指向该字段, 返回的地址将在结构体之外,如果此指针一直存活不释放对应的内存,就会有内存泄露的问题(该内存不因结构体释放而释放)。
因此,当 struct{}
作为其他 struct 最后一个字段时,需要填充额外的内存保证安全,如果 empty struct 在开始位置,或者中间位置,那么它的地址是下一个变量的地址
|
|
Empty 的使用场景
空结构体 struct{ }
为什么会存在的核心理由就是为了节省内存。当你需要一个结构体,但是却丝毫不关系里面的内容,那么就可以考虑空结构体。golang 核心的几个复合结构 map
,chan
,slice
都能结合 struct{}
使用。
map
& struct{}
|
|
chan
& struct{}
channel
和 struct{}
结合是一个最经典的场景,struct{}
通常作为一个信号来传输,并不关注其中内容。chan 的分析在前几篇文章有详细说明。chan 本质的数据结构是一个管理结构加上一个 ringbuffer ,如果 struct{}
作为元素的话,ringbuffer 就是 0 分配的。
chan
和 struct{}
结合基本只有一种用法,就是信号传递,空结构体本身携带不了值,所以也只有这一种用法啦,一般来说,配合 no buffer 的 channel 使用。
|
|
这种场景我们思考下,是否一定是非 struct{}
不可?其实不是,而且也不多这几个字节的内存,所以这种情况真的就只是不关心 chan
的元素值而已,所以才用的 struct{}
。
总结
- 空结构体也是结构体,只是 size 为 0 的类型而已;
- 所有的空结构体都有一个共同的地址:
zerobase
的地址; - 我们可以利用empty struct 不占用内存的特性,来优化代码,比如利用map 实现set 以及 chan 等。