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

推荐订阅源

U
Unit 42
N
News and Events Feed by Topic
S
Schneier on Security
G
GRAHAM CLULEY
Scott Helme
Scott Helme
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
GbyAI
GbyAI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
C
CERT Recently Published Vulnerability Notes
T
The Exploit Database - CXSecurity.com
C
Cisco Blogs
T
The Blog of Author Tim Ferriss
Cisco Talos Blog
Cisco Talos Blog
P
Privacy & Cybersecurity Law Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - 司徒正美
Blog — PlanetScale
Blog — PlanetScale
Project Zero
Project Zero
MyScale Blog
MyScale Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
The Last Watchdog
The Last Watchdog
Vercel News
Vercel News
The Cloudflare Blog
C
Check Point Blog
Help Net Security
Help Net Security
Microsoft Security Blog
Microsoft Security Blog
AI
AI
Simon Willison's Weblog
Simon Willison's Weblog
云风的 BLOG
云风的 BLOG
M
MIT News - Artificial intelligence
Stack Overflow Blog
Stack Overflow Blog
腾讯CDC
NISL@THU
NISL@THU
S
Security @ Cisco Blogs
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
S
SegmentFault 最新的问题
MongoDB | Blog
MongoDB | Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threatpost
AWS News Blog
AWS News Blog
Cloudbric
Cloudbric
N
News and Events Feed by Topic
PCI Perspectives
PCI Perspectives
S
Securelist
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V
Vulnerabilities – Threatpost
S
Secure Thoughts

hsfzxjy 的博客

解决 VSCode + CMake + MSVC 编译器信息乱码的问题 使用 3090 部署 1.58bit 动态量化版 DeepSeek R1 671b 如何在 VS Code DevContainer 中配置 HTTP 代理 如何在跳板机背后的服务器上使用 VS Code Remote - Containers Cohesive Digests for Ints and Floats 美术馆 一尺之槌,日取其半,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 回文计数算法 硬卧 Go Fact: Zero-sized Field at the Rear of a Struct Has Non-zero Size 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 Git-based Dependencies in Dart and Go Reversy Naming 人类一败涂地 Invalid Golang Pointers Can Bite You Even If You Don't Dereference Side Project(副业) A Flaw of Promoting Complex Trait Bounds in Rust Initialize Process Pool Worker with Individual Value Rust - Python FFI From Scratch [Extending Hexo For My Site] Part 1 [Extending Hexo For My Site] Part 0 Debug a 'torch.tensor(1).cuda()' hanging 不自由的互联网 Retrieve Contents over HTTP without curl or wget [Unravelling mocona] Part 1 - Verbosity or Anti-Pattern [Unravelling mocona] Part 0 - Preface Understanding pickle in Python Rough Notes on Deploying Vaultwarden & NextCloud Bookmarks 语言狂热者与实用主义者 Demystify the randomness in CUDA kernels Performant Bulk Mutations in IndexedDB Auto Rebuild .pyx Files with pyximport Cython and Threads Obtain a Random Available TCP Port with Bash Information Theory: KL Divergence Information Theory: Entropy and Mutual Information 铁板烧 西郊线 Proof of the Gumbel Max Trick Option::as_ref Rc, RefCell and Interior Mutability Visualizing Correlation 三月十日杂感 三月一日杂感 二月十一日杂感 一月二十六日杂感 SS Configuration 一月七日杂感 四月·病 Haskell 笔记:State Monad Haskell 笔记:Monad 引论 Haskell 笔记:Applicative Haskell 笔记:Category Theory and Functor Haskell 笔记:data, type, newtype Haskell 笔记:folds 使用 Aria2 在 Ubuntu 中下载百度云资源 从伪并行的 Python 多线程说起 一个 Reentrant Error 引发的对 Python 信号机制的探索和思考 Linux 文件权限 HSFZMUN 4.0 部署小记 午后雨·科大 最后的雨夜·广州 揭秘·变态的平方根倒数算法 神坑·Python 装饰类无限递归 Python“黑魔法”之 Encoding & Decoding Ubuntu 重新映射键盘布局 为什么我要翻墙 Python“黑魔法”之 Generator Coroutines 数学美 之 判断线段相交的最简方法 除夕杂感 17 行代码实现的简易 Javascript 字符串模板 Python“黑魔法”之 Meta Classes 诗集 生活,需要被“发现” 家书·十八岁成人礼 炫技?还是需求? 【译】响应式图片的现状 【译】“为什么有这么多的编程语言?” Wisecity 商赛总结——也谈前端自动化测试 记一次 DoS 诈骗网站的经历 那一年,我们望向星空
Rust 中的隐匿概念 —— Place(位置)
2024-11-10 · via hsfzxjy 的博客

在本文开始前,我们先看一个奇怪的问题。以下两个函数,为什么 reborrow_good 能通过编译,而 deref_bad 不能?

fn reborrow_good(p: *const i32) {
let _p = &raw const *p;
}

fn deref_bad(p: *const i32) {
let _v = *p;
}

&raw const B 是 Rust 1.82 中引入的新语法,作用是创建一个指向 B 的 raw 指针。
没有此语法前,为了获取指向 x.y 的 raw 指针,只能使用特殊宏 addr_of!(x.y)。而有了 &raw 语法后便可写为 &raw const x.y

使用 Edition 2024 的 Rust 编译器,deref_bad 会报如下错(Playground),提示我们解引用 raw 指针需要在 unsafe block 中进行:

error[E0133]: dereference of raw pointer is unsafe and requires unsafe block
--> src/lib.rs:6:14
|
6 | let _v = *p;
| ^^ dereference of raw pointer
|
= note: raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior

在 Rust 中,raw 指针相较引用,不受所有权规则的约束。它们可能传自其他语言,可能是空指针,可能没有对齐,可能有 alias 问题。因此,解引用 raw 指针需要在 unsafe 块中进行,而且要万分小心。

但这就奇怪了:明明 reborrow_good&raw const *p 中也有 *p 这一表达式,为什么它就能通过编译,而不需要 unsafe 呢?

拆解表达式

为了解答以上问题,我们不妨从编译器视角出发,对两个例子作拆解分析,设想编译器是怎么生成对应的伪指令序列的。

分析 reborrow_good

首先来看 _p = &raw const *p 这个表达式。

p 作为一个变量,它“不是一个值”。Rust 中的变量是对外部位置的一种抽象。一个变量可能对应 CPU 寄存器,也可能对应内存中的某个位置。为了知道变量里面的内容,我们先要 “读取”它,得到一个整数值。这一步可以记为 $val0 := load p。注意,我用带 $ 前缀的 $val0 表示值,从而与 p 等位置作区分。

有了 $val0 这个值,下一步是对其应用 * 解引用运算。解引用相当于拿着一个代表地址的整数,去寻找对应的内存位置。因此这一步的结果是一个位置,而不是一个值,可以记为 place0 := deref $val0

下一步是 &raw const 操作,即取地址操作。取地址相当于以一个位置作为输入,输出一个代表其地址的整数值。这步可记为 $val1 := addrof place0

最后,我们将 $val1 赋给 _p。这步可记为 store _p $val1。整个表达式的伪指令序列如下:

$val0  :=   load  p
place0 := deref $val0
$val1 := addrof place0
store _p $val1

分析 deref_bad

我们再来看 deref_bad 中的 _v = *p 表达式。这其中也有 *p 一项,因此前两条伪指令与上面一样,分别是 $val0 := load pplace0 := deref $val0

接下来,我们需要对 _v 变量赋值,而这个值是从 place0 中读取的,因此在 store 指令前,还需要一条 load place0 指令。最终,整个表达式的伪指令序列如下:

$val0  :=   load  p
place0 := deref $val0
$val1 := load place0
store _v $val1

对比分析

$val0  :=   load  p
place0 := deref $val0
$val1 := addrof place0
store _p $val1
$val0  :=   load  p
place0 := deref $val0
$val1 := load place0
store _v $val1

不难发现,两段序列的 不同之处在第三行:对于 place0reborrow_good 使用了 addrof,而 deref_bad 使用了 load。这意味着后者会产生对 place0 位置的访问,而前者没有。由于 place0 源自 *p,Rust 推断出 load place0 是不安全的,因此拒绝编译 deref_bad 的代码。

Place(位置)

经过以上分析,我们不难发现,Rust 中存在一种抽象概念,即Place(位置)。一个 Place 即代表了一块外部世界的存储空间。

Place 这一概念在语言标准中没有提及,各大教程中也没有介绍,但它确确实实存在于 Rust 的世界观中。事实上,它存在于 MIR(Mid-level IR)。MIR 是 Rust 编译器的一种中间表示。从 Rust 到 MIR 再到汇编,抽象程度逐渐降低,与物理世界的契合逐步增加。于是,MIR 需要一个概念,指涉物理世界广泛存在的“储存空间”——这便是 Place。

Rust 中隐匿的 Place 概念

Place 这一抽象始于低层的 MIR,却或多或少外溢到了高层的 Rust 中。然而 Rust 有意隐匿了这一抽象,进而造就了诸如 reborrow_good/deref_bad 合法性的困惑。

“解引用”一词常被理解为“寻址+读取”。但从上文的分析我们不难看出,表达式 *p 实则只通过“寻址”产生了一个 Place,而是否“读取”要看后续的操作,如在 deref_bad 中需要对 Place 取值再重赋值,这便涉及到了对 Place 的读取。Place 本身不会造成安全问题,即便其可能非法。真正带来安全隐患的是对 Place 的事实访问。

*p 产生一个 Place,因而也被称为 Place 表达式。除了 *p,还有其他一些常见的 Place 表达式,如单变量表达式 x,字段表达式 x.y 以及下标表达式 x[y] 等。对于 Place 表达式,我们可以进行“取址”、“读取”和“赋值”三种操作。“取址”和“赋值”分别对应 Rust 中的 &= 运算,但“读取”并没有类似的对应——Rust 会隐式地在合适的地方插入对 Place 的“读取”操作,淡化了这一步骤的重要性。

事实上,我们可以假想 Rust 中有个额外的 load 运算符。load 只能作用于 Place 之上,但编译器允许我们随意省略。在作类似分析时,为了看到问题的本质,我们可以显示加上省略的 load。还是以 _p = &raw const *p 为例,加上 load 后变为:

_p = &raw const *(load p)

_v = *p 变为:

如此一来,何处产生内存访问便一目了然。


理解 Place,对于理解 Rust 中 Reborrow 等奇特概念,以及 unsafe 代码的编写,都有着重要的帮助。这些问题我会在后续博客中分析。

参考


作者:hsfzxjy
链接:
许可:CC BY-NC-ND 4.0.
著作权归作者所有。本文不允许被用作商业用途,非商业转载请注明出处。