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

推荐订阅源

大猫的无限游戏
大猫的无限游戏
K
Kaspersky official blog
Apple Machine Learning Research
Apple Machine Learning Research
B
Blog
aimingoo的专栏
aimingoo的专栏
M
MIT News - Artificial intelligence
小众软件
小众软件
云风的 BLOG
云风的 BLOG
腾讯CDC
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Hugging Face - Blog
Hugging Face - Blog
S
SegmentFault 最新的问题
Stack Overflow Blog
Stack Overflow Blog
量子位
S
Secure Thoughts
G
GRAHAM CLULEY
C
CXSECURITY Database RSS Feed - CXSecurity.com
人人都是产品经理
人人都是产品经理
雷峰网
雷峰网
T
Threat Research - Cisco Blogs
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Cisco Talos Blog
Cisco Talos Blog
G
Google Developers Blog
爱范儿
爱范儿
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
有赞技术团队
有赞技术团队
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Martin Fowler
Martin Fowler
The GitHub Blog
The GitHub Blog
Google DeepMind News
Google DeepMind News
C
Cisco Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园 - 聂微东
宝玉的分享
宝玉的分享
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
Netflix TechBlog - Medium
Forbes - Security
Forbes - Security
Engineering at Meta
Engineering at Meta
S
Security Affairs
Help Net Security
Help Net Security
博客园 - 三生石上(FineUI控件)
AWS News Blog
AWS News Blog
博客园 - 叶小钗
Recent Commits to openclaw:main
Recent Commits to openclaw:main
V2EX - 技术
V2EX - 技术
Hacker News: Ask HN
Hacker News: Ask HN
Project Zero
Project Zero
H
Heimdal Security Blog
W
WeLiveSecurity
C
Check Point Blog

Posts on Changkun's Blog

Why High-Output Systems Are Often the First to Stop Growing Dark Forest Theory: A Formal Derivation Agents (or Humans) in Goal-Directed and Goalless Environments: On Pipelines, Priors, and the Rhythm Between Exploration and Exploitation At the Boundary of Self-Reference: From Stable Structures in Artificial Intelligence to the Self as a Recursive Model in an Open Dissipative System 2023 Reading List 2022 Reading List 2021 Reading List Are PSS/USS and RSS Actually the Same Thing? Performance Differences from Page Faults vs. Prefetching 2020 Year-End Review Migration with Zero Downtime 2020 Reading List The All in Go Stack Pointers Might Not Be Ideal for Parameters Eliminating A Source of Measurement Errors in Benchmarks Setup Wordpress in 10 Minutes 我为什么不再写博客了? 2019 年终总结 2018-2019 读书清单 Ten years of blogging Rethinking the Reflections on Communications and Trusts 2018 年终总结 Go source code study is open source Go source study: sync.Pool Go runtime programming A Million WebSocket and Go Designing Asynchronous RESTful APIs 分布式杂谈01:CAP 理论的误解 Issues of Human-Bot Interaction 压缩法与深度网络的泛化性 Go in 1 Hour UMSLT04: The Past and Present of SGD UMSLT03: A Gentle Start of Learning Theory UMSLT02: A Breif History of Neural Networks UMSLT01: A Breif History of Regularization 不笑不足以为道 论文笔记:Generalization in Deep Learning 2017 年终总结 2017 读书清单 深度学习的泛化理论简介 删除 GitHub 上已经提交的敏感信息 硕士生涯的第一年就这样告一段落了 人肉计算(10): 系统参与激励 人肉计算(9): 陷阱的解法 别聊,一聊你就暴露 人肉计算(8): 人肉计算与数据科学中的陷阱 人肉计算(7): 社会行为分析 Hexo + GitHub + Travis CI + VPS 自动部署 人肉计算(6): 预测市场 人肉计算(5): 信用风险评级模型 读书与回报 瞎扯: 对现代企业理论与当下IT企业的商业模式和信息产业链的规律性的思考 人肉计算(4): 输入数据聚合与PageRank 又一次打整了一下博客 人肉计算(3): 输入数据聚合与链路预测 人肉计算(2): 意图博弈 GWAPs 人肉计算(1): 众包与群众智慧 对后辈同学在计算机专业上的答疑与解惑 在德国的医疗及住院体验 这可能不是一个技术博客了 实验楼楼赛第3期-Python-题解 迅速更换了 DISQUS Electron 深度实践总结 良好的编码体验的三个方面 2016 年终总结 2016 读书清单 最近在着手写的文章 微信小程序文档极致总结 谈谈过去三个月在实验楼的实习经历 Built a Desktop Client for My Blog Guacamole 源码分析与 VNC 中 RFB 协议的坑 《高速上手 C++11/14》正式发布 Docker 极速入门教程02 - 镜像与容器管理 Docker 极速入门教程01 - 基本概念和操作 阶段性沉默 ELK+Redis 最佳实践 终于全面启用了 HTTPS 苹果开源了LZFSE无损压缩 Hash 碰撞的一种思路 记一次完整的 Kaldi-TIMIT 示例运行 Kaldi 上的 TIMIT 例子 Kaldi 安装与部署 从科研写作谈起 Swift API 设计指南 有趣的人类 所以其实论文并没有什么鬼用 Githug 通关记录及指南 小结一下这学期的收获 2015 读书清单 2015 年终总结 负能量爆表 转眼就快两个月了 博客迁移记录 大三总结 这个世界,终究不会是我们的。 Linux 内核分析 之六:Linux 内核创建进程的过程 小说「泽缘」 Linux 内核分析 之五:system_call中断处理过程的简要分析 大创项目的标题真是每年都在考验同学们的想象力啊 Linux 内核分析 之四:使用库函数API和嵌入汇编两种方式使用同一个系统调用
Go source study: unsafe Pattern
Changkun Ou · 2018-09-27 · via Posts on Changkun's Blog

Published at发布于:   |   PV/UV: /   |   Reading阅读: 3 min

Go 源码研究:unsafe 范式

unsafe 包该包会绕过所有的 Go 类型安全检查,几乎是 Cgo 的灵魂,也是支撑 Go 运行时、reflect、系统调用等机制的核心。

任意类型 ArbitraryType

ArbitraryType 只用于文档展示的目的,其本身并非 unsafe 包的一部分。它表示了任意 Go 表达式的类型。

Go 指针 Pointer

Pointer 表示指向任意类型的指针。有四种特殊操作可以用于指针类型而不能用于其他类型:

  • 任意类型的指针值可以转换为 Pointer
  • Pointer 均可转换为任意类型的指针值
  • uintptr 均可以转换为 Pointer
  • Pointer 均可以转换为 uintptr

因此 Pointer 允许程序破坏类型系统并对任意的内存进行读写。使用应非常小心。

1
type Pointer *ArbitraryType

unsafe.Pointer 可以在下面提及的这些情况下安全的使用。没有使用以下模式的代码可能会失效。 即便是下面提到的有效模式也伴随一些重要的警告。

运行 go vet 可以帮助寻找使用 unsafe.Pointer 是否准讯下面这些模式,但没有运行 go vet 的代码则不会有任何保证。

情况 1: 将 *T1 转换为 *T2.

提供的 T2 不比 T1 大,且双方拥有相同的内存布局。该转换允许将数据以另一种类型进行表示。 例如 math.Float64bits 的实现:

1
2
3
func Float64bits(f float64) uint64 {
    return *(*uint64)(unsafe.Pointer(&f))
}

情况 2: 将 Pointer 转换为 uintptr (但不转换回 Pointer).

将一个 Pointer 转换到 uintptr 会将该值指向的内存地址转为整型。一个常见的用法是 用于打印输出。将 uintptr 转回 Pointer 通常情况下是不允许的。uintptr 是一个整数, 而非引用。将一个 Pointer 转换到 uintptr 会创建一个没有指针语义的整数值。 即使一个 uintptr 保留了某个对象的地址,垃圾回收如果移动了对象,也不会更新 uintptr 的值。 uintptr 也不会作为对象不被回收的依据。

剩下的模式枚举了从 uintptr 转到 Pointer 的所有有效转换。

情况 3: 将 Pointer 转换为 uintptr 再转回,包含运算

如果 p 指向一个已分配的对象,则可以先通过该对象转换到 uintptr,添加偏移量,再转换回 Pointer

1
p = unsafe.Pointer(uintptr(p) + offset)

最常见的用法是访问结构体中的字段或则数组的元素:

1
2
3
4
// 等价于 f := unsafe.Pointer(&s.f)
f := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f))
// equivalent to e := unsafe.Pointer(&x[i])
e := unsafe.Pointer(uintptr(unsafe.Pointer(&x[0])) + i*unsafe.Sizeof(x[0]))

这种方式增加和减少指针的偏移量都是有效的,使用 &^ 同样有效,通常用于对齐。 所有情况下,指针必须指向原始分配的对象。与 C 不同,将指针移动移至原有内存区外是无效的:

1
2
3
4
5
6
// 无效: end 指针在已分配内存区外
var s thing
end = unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Sizeof(s))
// 无效: end 指针在已分配内存区外
b := make([]byte, n)
end = unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(n))

注意,两个转换必须出现在同一个表达式中,他们之间只有普通的计算行为:

1
2
3
// 无效: uintptr 在转回 Pointer 时不能存储在一个变量中
u := uintptr(p)
p = unsafe.Pointer(u + offset)

情况 4: 当调用 syscall.Syscall 时候将 Pointer 转换为 uintptr

syscall 包中的 Syscall 函数直接传递 uintptr 参数给操作系统,根据调用的细节, 可以将他们中的一些重新解释为指针。也就是说,系统调用实现隐式地将某些参数从 uintptr 转换回指针。 也就是说,如果必须将指针参数转换为 uintptr 用作参数,则改转换必须出现在表达式本身之中:

1
syscall.Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(n))

编译器处理了在调用汇编代码的函数参数表中将一个 Pointer 转换为 uintptr 的情况。 通过引用分配的对象(如果有),则在调用完成前都不会被移动,及时该变量可能不再需要。 为了让编译器识别这种模式,这种转换必须出现在参数表中:

1
2
3
// 无效: uintptr 不能存储在变量中
u := uintptr(unsafe.Pointer(p))
syscall.Syscall(SYS_READ, uintptr(fd), u, uintptr(n))

情况 5: 将 reflect.Value.Pointerreflect.Value.UnsafeAddr 返回的 uintptr 转换到 Pointer

reflect 包的 Value 方法将 PointerUnsafeAddr 返回为一个 uintptr 而非 unsafe.Pointer 用以防止调用者在不导入 unsafe 的情况下使用结果更改任意类型。但这个结果非常脆弱,并且必须在调用后 在同一表达式内立即转为指针:

1
p := (*int)(unsafe.Pointer(reflect.ValueOf(new(int)).Pointer()))

由于上面的情况,转换前进行任何存储都是无效的:

1
2
3
// 无效: uintptr 在转回 Pointer 前不能被存储在一个变量中
u := reflect.ValueOf(new(int)).Pointer()
p := (*int)(unsafe.Pointer(u))

情况 6: 将 reflect.SliceHeaderreflect.StringHeader Data 字段与 Pointer 的互相转换

与前面的情况一样,反射数据结构 SliceHeaderStringHeaderData 字段声明为 uintptr, 以防止调用者在不导入 unsafe 的情况下修改为任意类型。但是,这意味着 SliceHeaderStringHeader 仅在解释实际切片或字符串值的内容时有效。

1
2
3
4
var s string
hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) // 情况 1
hdr.Data = uintptr(unsafe.Pointer(p))              // 情况 6 (此情况)
hdr.Len = n

在这种用法中,hdr.Data 实际上是一种引用字符串投的底层指针的替代方案,而非 uintptr 变量本身。 通常 reflect.SliceHeaderreflect.StringHeader 只能作 *reflect.SliceHeader*reflect.StringHeader 指向实际的切片或字符串,而不是普通的结构。 程序不应声明或分配这些结构类型的变量。

1
2
3
4
5
// 无效: 一个直接声明的 header 不会被 Data 作为引用保存
var hdr reflect.StringHeader
hdr.Data = uintptr(unsafe.Pointer(p))
hdr.Len = n
s := *(*string)(unsafe.Pointer(&hdr)) // p 可能已经丢失

指针操作

Sizeof 返回任意类型 x 的表达式所占用的字节数。该大小不包括 x 占用的内存。 例如,x 是一个 slice,则 Sizeof 返回 slice 描述符的大小,而非 slice 指向的内存块的大小。

1
func Sizeof(x ArbitraryType) uintptr

Offsetof 返回由 x 所代表的结构中字段的偏移量,它必须为 stuctValue.field 的形式。 换句话说,它返回了该结构起始处于该字段起始数之间的字节数。

1
func Offsetof(x ArbitraryType) uintptr

Alignof 返回任意类型的表达式 x 的对齐方式。其返回值 m 满足变量 v 的类型地址与 m 取模为 0 的最大值。 它与 reflect.TypeOf(x).Align() 返回的值相同。 作为特殊情况,一个变量 s 如果是结构体类型且 f 是结构体的一个字段,那么 Alignof(s.f) 将返回 结构体内部该类型要求对齐的值,与 reflect.TypeOf(s.f).FieldAlign() 值相同。

1
func Alignof(x ArbitraryType) uintptr