前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Go高性能编程 EP1 : empty struct

Go高性能编程 EP1 : empty struct

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  
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
运行
AI代码解释
复制
// 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
运行
AI代码解释
复制
//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
运行
AI代码解释
复制
//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
运行
AI代码解释
复制
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
运行
AI代码解释
复制
// 创建 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
运行
AI代码解释
复制
// 创建一个信号通道
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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
理解go中空结构体的应用和实现原理
那为什么要这样使用空结构体呢?今天就跟大家一起来学习下空结构体的应用以及底层原理。
Go学堂
2023/01/31
3910
空结构体struct{}解析
本篇文章转自David的"The empty struct"一文,原文地址链接是http://dave.cheney.net/2014/03/25/the-empty-struct。 Introduction 这篇文章详细介绍了我最喜欢的Go数据类型,空结构体--struct{}。 空结构体是没有位段的结构体,以下是空结构体的一些例子: type Q struct{}var q struct{} 但是如果一个就结构体没有位段,不包含任何数据,那么他的用处是什么?我们能够利用空结构体完成什么任务? Width
李海彬
2018/03/21
2.1K0
空结构体引发的大型打脸现场
哈喽,大家好,我是正在学习PS技术的asong,上周读者问了我一道题,觉得挺有意义的,就在这里分享一下,我们先来看一下这个题:
Golang梦工厂
2022/07/08
1960
空结构体引发的大型打脸现场
Go 空结构体:零内存的魔力
在 Go 语言中,有一种特殊的用法可能让许多人感到困惑,那就是空结构体 struct{}。在本文中,我将对 Go 空结构体进行详解,准备好了吗?准备一杯你最喜欢的咖啡或茶,随着本文一探究竟吧。
陈明勇
2023/11/01
5300
Go 空结构体:零内存的魔力
Go高性能编程EP3: 内存对齐
本文是Go语言高性能编程第三篇,分析了为什么需要内存对齐,Go语言内存对齐的规则,以及实际例子中内存对齐的使用,最后分享了两个工具,帮助我们在开发过程中发现内存对齐问题。
萝卜要努力
2025/03/07
890
Go高性能编程EP3: 内存对齐
你不知道的Go unsafe.Pointer uintptr原理和玩法
这个类型比较重要,它是实现定位和读写的内存的基础,Go runtime大量使用它。官方文档对该类型有四个重要描述:
sunsky
2020/08/20
1.8K0
你不知道的Go unsafe.Pointer uintptr原理和玩法
再不Go就来不及了!Go高性能编程技法解读
导语 | 代码的稳健、可读和高效是我们每一个coder的共同追求。本文将结合Go语言特性,为书写高效的代码,力争从常用数据结构、内存管理两个方面给出相关建议。话不多说,让我们一起学习Go高性能编程的技法吧。 一、常用数据结构 (一)反射虽好,切莫贪杯 标准库reflect为Go语言提供了运行时动态获取对象的类型和值以及动态创建对象的能力。反射可以帮助抽象和简化代码,提高开发效率。 Go语言标准库以及很多开源软件中都使用了Go语言的反射能力,例如用于序列化和反序列化的json、ORM框架 gorm、xorm等
腾讯云开发者
2022/03/29
8580
Go 高性能编程技法
作者:dablelv,腾讯 IEGggG 后台开发工程师 代码的稳健、可读和高效是我们每一个 coder 的共同追求。本文将结合 Go 语言特性,为书写效率更高的代码,从常用数据结构、内存管理和并发,三个方面给出相关建议。话不多说,让我们一起学习 Go 高性能编程的技法吧。 常用数据结构 1.反射虽好,切莫贪杯 标准库 reflect 为 Go 语言提供了运行时动态获取对象的类型和值以及动态创建对象的能力。反射可以帮助抽象和简化代码,提高开发效率。 Go 语言标准库以及很多开源软件中都使用了 Go 语言的反
腾讯技术工程官方号
2022/03/18
2.1K0
5.Go编程快速入门学习
描述: Go语言中目前(1.16 版本中)是没有异常处理机制(Tips :说是在2.x版本中将会加入异常处理机制),但我们可以使用error接口定义以及panic/recover函数来进行异常错误处理。
全栈工程师修炼指南
2022/09/29
7860
5.Go编程快速入门学习
go 高并发下的数据结构是怎样?
0 字节的变量在内存中的地址是相同的,称为 zerobase,这个变量在 runtime/malloc.go 文件中
阿珍
2024/07/21
770
go 高并发下的数据结构是怎样?
Go看源码必会知识之unsafe包
众所周知,Go语言被设计成一门强类型的静态语言,那么他的类型就不能改变了,静态也是意味着类型检查在运行前就做了。所以在Go语言中是不允许两个指针类型进行转换的,使用过C语言的朋友应该知道这在C语言中是可以实现的,Go中不允许这么使用是处于安全考虑,毕竟强制转型会引起各种各样的麻烦,有时这些麻烦很容易被察觉,有时他们却又隐藏极深,难以察觉。大多数读者可能不明白为什么类型转换是不安全的,这里用C语言举一个简单的例子:
Golang梦工厂
2022/07/08
3100
Go看源码必会知识之unsafe包
Go内存对齐详解
在《小许code:Go内存管理和分配策略》这篇分享中我们了解到Go是怎么对内存进行管理和分配的,那么用户的程序进程在linux系统中的内存布局是什么样的呢?我们先了解一下基础知识,然后再看Go的内存对齐。
小许code
2023/04/14
2.1K0
Go内存对齐详解
详解Go变量类型的内存布局
每当我们编写任何程序时,我们都需要在内存中存储一些数据/信息。数据存储在特定地址的存储器中。内存地址看起来像0xAFFFF(这是内存地址的十六进制表示)。
sunsky
2020/08/20
1.9K0
详解Go变量类型的内存布局
Go 语言切片的三种特殊状态 —— 90% 的开发者都忽视了
我们今天要来讲一个非常细节的小知识,这个知识被大多数 Go 语言的开发者无视了,它就是切片的三种特殊状态 —— 「零切片」、「空切片」和「nil 切片」。
老钱
2018/12/13
9930
Go语言高性能编程手册(王权富贵)
代码的稳健、可读和高效是我们每一个 coder 的共同追求。本文将结合 Go 语言特性,为书写效率更高的代码,从常用数据结构、内存管理和并发,三个方面给出相关建议。话不多说,让我们一起学习 Go 高性能编程的技法吧。
曾高飞
2025/06/16
380
【Go】深入剖析slice和array
array 和 slice 看似相似,却有着极大的不同,但他们之间还有着千次万缕的联系 slice 是引用类型、是 array 的引用,相当于动态数组, 这些都是 slice 的特性,但是 slice 底层如何表现,内存中是如何分配的,特别是在程序中大量使用 slice 的情况下,怎样可以高效使用 slice? 今天借助 Go 的 unsafe 包来探索 array 和 slice 的各种奥妙。
thinkeridea
2019/11/04
4950
【Go】深入剖析slice和array
Go 编程 | 连载 16 - 结构体 Struct
在基本数据类型中的 byte 和 rune 其实就是 uint8 和 int32 的别名,在源码中这些别名就是使用 type 关键字定义的,当然我们也可以自己定义别名。
RiemannHypothesis
2022/09/28
3240
go 指针和内存分配详解
每当我们编写任何程序时,我们都需要在内存中存储一些数据/信息。数据存储在特定地址的存储器中。内存地址看起来像0xAFFFF(这是内存地址的十六进制表示)。
孤烟
2020/09/27
1K0
详解内存对齐
在了解内存对齐之前,先来明确几个关于操作系统的概念,更加方面我们对内存对齐的理解。
Golang梦工厂
2022/07/11
1.3K0
详解内存对齐
《Go小技巧&amp;易错点100例》第三十一篇
在计算机系统中,CPU 访问内存时并不是逐字节读取的,而是以特定大小的块(通常为 4/8 字节)为单位进行读取。当数据的内存地址正好是其大小的整数倍时,称为自然对齐。Go 编译器会根据平台特性自动进行内存对齐优化,这种机制虽然可能产生填充字节,但能大幅提升内存访问效率。
闫同学
2025/04/28
1030
《Go小技巧&amp;易错点100例》第三十一篇
相关推荐
理解go中空结构体的应用和实现原理
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验