惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
Microsoft Security Blog
Microsoft Security Blog
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Last Week in AI
Last Week in AI
罗磊的独立博客
腾讯CDC
云风的 BLOG
云风的 BLOG
月光博客
月光博客
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 三生石上(FineUI控件)
宝玉的分享
宝玉的分享
U
Unit 42
I
InfoQ
D
DataBreaches.Net
Blog — PlanetScale
Blog — PlanetScale
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V
V2EX
美团技术团队
IT之家
IT之家
Stack Overflow Blog
Stack Overflow Blog
F
Fortinet All Blogs
GbyAI
GbyAI
S
SegmentFault 最新的问题

hsfzxjy 的博客

解决 VSCode + CMake + MSVC 编译器信息乱码的问题 使用 3090 部署 1.58bit 动态量化版 DeepSeek R1 671b 如何在 VS Code DevContainer 中配置 HTTP 代理 如何在跳板机背后的服务器上使用 VS Code Remote - Containers Cohesive Digests for Ints and Floats Rust 中的隐匿概念 —— Place(位置) 美术馆 一尺之槌,日取其半,1075日而竭 老生常谈:使用 Cloudflare 自选 IP 加速站点访问 辩义 State、Nation 与 Country 将 Base64 编码的数据快速转换为 Uint8Array 折腾 NPU·第1章 —— 搭建 Level Zero 开发环境 折腾 NPU·第0章 —— Intel NPU 概述与 Level-Zero 新增域名 monad.run CSS 中为特定字符设置不同字体 Arbitary Lifetime Transmutation via Rust Unsoundness Dijkstra 算法的延伸 Manacher 回文计数算法 硬卧 Display *big.Rat Losslessly and Smartly in Golang 代码的仪式 Building Electron From Scratch 中式亲属称谓研究之一:构建半群 Some Notes on Kotlin Coroutines Git sparse-checkout and partial clones for Mega-Repos 辩义“封建” Diving from the CUDA Error 804 into a bug of libnvidia-container Modern Cryptography, GPG and Integration with Git(hub) Move the Root Partition of Ubuntu A New Programmer Kicks a Roadblock
Go Fact: Zero-sized Field at the Rear of a Struct Has Non...
2023-11-04 · via hsfzxjy 的博客
See the discussion of this article on Reddit.

There’s a concept in Golang called zero-sized type (or ZST), namely, a type whose variables take up zero bit of memory. One of them is the famous struct{}. People often use map[string]struct{} to efficiently emulate a set structure. Others include zero-length arrays such as [0]int, albeit not very common, are adopted to enforce some properties of a customized type.

type UserName struct{
name string

_phantom [0]func()
}
println(UserName{name: "a"} == UserName{name: "b"})


type Tag struct{
Value string
_tag struct{}
}

type Id struct{
Value string
_id struct{}
}

var a = Tag{Value: "Go"}
var b = Id(a)

One may think that a ZST variable always occupies 0 byte of space, which however is not the case. For example, the result of unsafe.Sizeof against UserName might surprise you:

println(unsafe.Sizeof(UserName{}), unsafe.Sizeof("")) 

The size of UserName is 8 bytes greater than the size of a string, which means the field of [0]func() at the rear takes up exactly 8 bytes.

Well, this is heavy. The incorporation of ZSTs was aimed to ensure type safety without introducing any runtime overhead. However, if this implementation leads to an increased footprint, it may be considered less favorable. Why does it happen?

There’s an explanation from the Go team on Github:

[…] this means it (a pointer to ZST field) would actually point outside of the allocation.
Unlike many other languages, (C, C++, Rust, …) where creating pointer past the allocation (but not using it) is legal. In go it isn’t because the GC may at anytime inspect any pointer.

This design serves as a precautionary measure to avoid any potential confusion for the Garbage Collector runtime. Roughly speaking, Go organizes allocated objects into several memory blocks. At the same time, Go provides users the flexibility to create pointers pointing to or within those objects. These pointers are inspected by the garbage collector from time to time, therefore they must point to somewhere valid within a memory block. Now think about an edge case:

          memory block
______________/\______________
/ \
+-----+------------------------+----------------------+
| ... | name | <unallocated memory> |
+-----+------------------------+----------------------+
\__________ __________/|
\/ |
u := UserName{} |
\|/
p := &(u._phantom)

In this case, an object u of type UserName is placed at the end of a memory block. Meanwhile, a pointer p exists and points to its _phantom field. If the _phantom field took up zero byte, the pointer p would contain the address of right boundary of the memory block, which, once dereferenced by the GC, would lead to invalid access to unallocated memory. Hence, the Go compiler reserves additional bytes at the end of the UserName struct to prevent the occurrence of such cases.

That said, we do have solution to eliminate such overhead. As discussed in the same Github issue, reordering the ZST field to the middle of the struct eliminates the need for additional reserved bytes. This adjustment ensures that there is no possibility of a pointer pointing to the allocation boundary.

type UserName2 struct{
_phantom [0]func()
name string
}
println(unsafe.Sizeof(Username2{}))

Hence, whenever we want to employ the “ZST-trick”, it is crucial to ensure its placement at the middle of a struct.


Author: hsfzxjy.
Link: .
License: CC BY-NC-ND 4.0.
All rights reserved by the author.
Commercial use of this post in any form is NOT permitted.
Non-commercial use of this post should be attributed with this block of text.