首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Go高性能编程 EP1 : empty struct

Go高性能编程 EP1 : empty struct

作者头像
萝卜要努力
发布2025-03-07 16:09:57
发布2025-03-07 16:09:57
12500
代码可运行
举报
文章被收录于专栏:萝卜要加油萝卜要加油
运行总次数:0
代码可运行

在 go语言中,正常的 struct 一定是需要占用一块内存的,但是有一种特殊情况,如果是一个空struct,那么它的大小为0. 这是怎么回事,空struct 又有什么用呢?

代码语言:javascript
代码运行次数:0
运行
复制
  
type Test struct {  
    A int  
    B string  
}    
func main() {  
    fmt.Println(unsafe.Sizeof(new(Test)))  
    fmt.Println(unsafe.Sizeof(struct{}{}))  
}  
/*  
8  
0  
*/

Empty Struct 的 秘密

特殊变量:zerobase

空结构体是没有内存大小的结构体。这句话是没有错的,但是更准确的来说,其实是有一个特殊起点的,那就是 zerobase 变量,这是一个 uintptr 全局变量,占用 8 个字节。当在任何地方定义无数个 struct {} 类型的变量,编译器都只是把这个 zerobase 变量的地址给出去。换句话说,在 golang 里面,涉及到所有内存 size 为 0 的内存分配,那么就是用的同一个地址 &zerobase 。 case1[1]

代码语言:javascript
代码运行次数:0
运行
复制
// https://go.dev/play/p/WNxfXviET_i
package main

import"fmt"

type emptyStruct struct {}

funcmain() {
    a := struct{}{}
    b := struct{}{}
    c := emptyStruct{}

    fmt.Printf("%p\n", &a)
    fmt.Printf("%p\n", &b)
    fmt.Printf("%p\n", &c)
}

// 0x58e360
// 0x58e360
// 0x58e360

空结构体的变量的内存地址都是一样的。这是因为编译器在编译期间,遇到 struct {} 这种特殊类型的内存分配,会给他分配&zerobase,这个代码逻辑是在 mallocgc[2] 函数里面:

代码语言:javascript
代码运行次数:0
运行
复制
//go:linkname mallocgc  
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {  
    ....
    if size == 0 {  
       return unsafe.Pointer(&zerobase)  
    }
    .....

这就是Empty struct 的秘密有了这个特殊的 变量,我们利用它可以完成很多功能。

Empty struct 与内存对其 一般情况下,struct 中包含 empty struct ,这个字段是不占用内存空间的,但是有一种情况是特殊的,那就是 empty struct 位于最后一位,它会触发内存对齐 。 case [3]

代码语言:javascript
代码运行次数:0
运行
复制
//https://go.dev/play/p/HcxlywljovS
type A struct {
    x int
    y string
    z struct{}
}
type B struct {
    x int
    z struct{}
    y string
}

funcmain() {
println(unsafe.Alignof(A{}))
println(unsafe.Alignof(B{}))
println(unsafe.Sizeof(A{}))
println(unsafe.Sizeof(B{}))
}

/**
8
8
32
24
**/

因为如果有指针指向该字段, 返回的地址将在结构体之外,如果此指针一直存活不释放对应的内存,就会有内存泄露的问题(该内存不因结构体释放而释放)。 因此,当 struct{} 作为其他 struct 最后一个字段时,需要填充额外的内存保证安全,如果 empty struct 在开始位置,或者中间位置,那么它的地址是下一个变量的地址

代码语言:javascript
代码运行次数:0
运行
复制
type A struct {  
    x int
    y string
    z struct{}  
}  
type B struct {  
    x int
    z struct{}  
    y string
}  

funcmain() {  
    a := A{}  
    b := B{}  
    fmt.Printf("%p\n", &a.y)  
    fmt.Printf("%p\n", &a.z)  
    fmt.Printf("%p\n", &b.y)  
    fmt.Printf("%p\n", &b.z)  
}
/**
0x1400012c008
0x1400012c018
0x1400012e008
0x1400012e008
**/

Empty 的使用场景

空结构体 struct{ } 为什么会存在的核心理由就是为了节省内存。当你需要一个结构体,但是却丝毫不关系里面的内容,那么就可以考虑空结构体。golang 核心的几个复合结构 mapchanslice 都能结合 struct{} 使用。

map & struct{}
代码语言:javascript
代码运行次数:0
运行
复制
// 创建 map
m := make(map[int]struct{})
// 赋值
m[1] = struct{}{}
// 判断 key 键存不存在
_, ok := m[1]
chan & struct{}

channelstruct{} 结合是一个最经典的场景,struct{} 通常作为一个信号来传输,并不关注其中内容。chan 的分析在前几篇文章有详细说明。chan 本质的数据结构是一个管理结构加上一个 ringbuffer ,如果 struct{} 作为元素的话,ringbuffer 就是 0 分配的。

chanstruct{} 结合基本只有一种用法,就是信号传递,空结构体本身携带不了值,所以也只有这一种用法啦,一般来说,配合 no buffer 的 channel 使用。

代码语言:javascript
代码运行次数:0
运行
复制
// 创建一个信号通道
waitc := make(chanstruct{})

// ...
goroutine 1:
// 发送信号: 投递元素
    waitc <- struct{}
// 发送信号: 关闭
close(waitc)

goroutine 2:
select {
// 收到信号,做出对应的动作
case <-waitc:
    }    

这种场景我们思考下,是否一定是非 struct{} 不可?其实不是,而且也不多这几个字节的内存,所以这种情况真的就只是不关心 chan 的元素值而已,所以才用的 struct{}

总结

  1. 空结构体也是结构体,只是 size 为 0 的类型而已;
  2. 所有的空结构体都有一个共同的地址:zerobase 的地址;
  3. 我们可以利用empty struct 不占用内存的特性,来优化代码,比如利用map 实现set 以及 chan 等。

参考链接

  1. The empty struct, Dave Cheney[4]
  2. Go 最细节篇— struct{} 空结构体究竟是啥?[5]

引用链接

[1]case1: https://go.dev/play/p/WNxfXviET_i

[2]mallocgc: https://go.googlesource.com/go/blob/fe36ce669c1a452d2b0e81108a7e07674b50692a/src/runtime/malloc.go#L983

[3]case : https://go.dev/play/p/HcxlywljovS

[4]The empty struct, Dave Cheney: https://dave.cheney.net/2014/03/25/the-empty-struct

[5]Go 最细节篇— struct{} 空结构体究竟是啥?: https://www.qiyacloud.cn/2020/12/2020-12-21/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-01-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 萝卜要加油 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Empty Struct 的 秘密
    • 特殊变量:zerobase
  • Empty 的使用场景
    • map & struct{}
    • chan & struct{}
  • 总结
  • 参考链接
  • 引用链接
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档