|
|
|
@ -2,24 +2,24 @@
|
|
|
|
|
|
|
|
|
|
古龙有一部小说,名为"七种兵器",其中每一种都精妙绝伦,令人闻风丧胆,而 `unsafe` 也有五种兵器,它们可以让你拥有其它代码无法实现的能力,同时它们也像七种兵器一样令人闻风丧胆,下面一起来看看庐山真面目。
|
|
|
|
|
|
|
|
|
|
## 解引用原生指针
|
|
|
|
|
## 解引用裸指针
|
|
|
|
|
|
|
|
|
|
原生指针(raw pointer) 又称裸指针,在功能上跟引用类似,同时它也需要显式地注明可变性。但是又和引用有所不同,原生指针长这样: `*const T` 和 `*mut T`,它们分别代表了不可变和可变。
|
|
|
|
|
裸指针(raw pointer) 又称裸指针,在功能上跟引用类似,同时它也需要显式地注明可变性。但是又和引用有所不同,裸指针长这样: `*const T` 和 `*mut T`,它们分别代表了不可变和可变。
|
|
|
|
|
|
|
|
|
|
大家在之前学过 `*` 操作符,知道它可以用于解引用,但是在原生指针 `*const T` 中,这里的 `*` 只是类型名称的一部分,并没有解引用的含义。
|
|
|
|
|
大家在之前学过 `*` 操作符,知道它可以用于解引用,但是在裸指针 `*const T` 中,这里的 `*` 只是类型名称的一部分,并没有解引用的含义。
|
|
|
|
|
|
|
|
|
|
至此,我们已经学过三种类似指针的概念:引用、智能指针和原生指针。与前两者不同,原生指针:
|
|
|
|
|
至此,我们已经学过三种类似指针的概念:引用、智能指针和裸指针。与前两者不同,裸指针:
|
|
|
|
|
|
|
|
|
|
- 可以绕过 Rust 的借用规则,可以同时拥有一个数据的可变、不可变指针,甚至还能拥有多个可变的指针
|
|
|
|
|
- 并不能保证指向合法的内存
|
|
|
|
|
- 可以是 `null`
|
|
|
|
|
- 没有实现任何自动的回收 (drop)
|
|
|
|
|
|
|
|
|
|
总之,原生指针跟 C 指针是非常像的,使用它需要以牺牲安全性为前提,但我们获得了更好的性能,也可以跟其它语言或硬件打交道。
|
|
|
|
|
总之,裸指针跟 C 指针是非常像的,使用它需要以牺牲安全性为前提,但我们获得了更好的性能,也可以跟其它语言或硬件打交道。
|
|
|
|
|
|
|
|
|
|
#### 基于引用创建原生指针
|
|
|
|
|
#### 基于引用创建裸指针
|
|
|
|
|
|
|
|
|
|
下面的代码**基于值的引用**同时创建了可变和不可变的原生指针:
|
|
|
|
|
下面的代码**基于值的引用**同时创建了可变和不可变的裸指针:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
let mut num = 5;
|
|
|
|
@ -28,9 +28,9 @@ let r1 = &num as *const i32;
|
|
|
|
|
let r2 = &mut num as *mut i32;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`as` 可以用于强制类型转换,在[之前章节](https://course.rs/basic/converse.html)中有讲解。在这里,我们将引用 `&num / &mut num` 强转为相应的原生指针 `*const i32 / *mut i32`。
|
|
|
|
|
`as` 可以用于强制类型转换,在[之前章节](https://course.rs/basic/converse.html)中有讲解。在这里,我们将引用 `&num / &mut num` 强转为相应的裸指针 `*const i32 / *mut i32`。
|
|
|
|
|
|
|
|
|
|
细心的同学可能会发现,在这段代码中并没有 `unsafe` 的身影,原因在于:**创建原生指针是安全的行为,而解引用原生指针才是不安全的行为** :
|
|
|
|
|
细心的同学可能会发现,在这段代码中并没有 `unsafe` 的身影,原因在于:**创建裸指针是安全的行为,而解引用裸指针才是不安全的行为** :
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
fn main() {
|
|
|
|
@ -44,16 +44,16 @@ fn main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 基于内存地址创建原生指针
|
|
|
|
|
#### 基于内存地址创建裸指针
|
|
|
|
|
|
|
|
|
|
在上面例子中,我们基于现有的引用来创建原生指针,这种行为是很安全的。但是接下来的方式就不安全了:
|
|
|
|
|
在上面例子中,我们基于现有的引用来创建裸指针,这种行为是很安全的。但是接下来的方式就不安全了:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
let address = 0x012345usize;
|
|
|
|
|
let r = address as *const i32;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
这里基于一个内存地址来创建原生指针,可以想像,这种行为是相当危险的。试图使用任意的内存地址往往是一种未定义的行为(undefined behavior),因为该内存地址有可能存在值,也有可能没有,就算有值,也大概率不是你需要的值。
|
|
|
|
|
这里基于一个内存地址来创建裸指针,可以想像,这种行为是相当危险的。试图使用任意的内存地址往往是一种未定义的行为(undefined behavior),因为该内存地址有可能存在值,也有可能没有,就算有值,也大概率不是你需要的值。
|
|
|
|
|
|
|
|
|
|
同时编译器也有可能会优化这段代码,会造成没有任何内存访问发生,甚至程序还可能发生段错误(segmentation fault)。**总之,你几乎没有好的理由像上面这样实现代码,虽然它是可行的**。
|
|
|
|
|
|
|
|
|
@ -82,7 +82,7 @@ fn main() {
|
|
|
|
|
"The {} bytes at 0x{:X} stored: {}",
|
|
|
|
|
length, pointer, message
|
|
|
|
|
);
|
|
|
|
|
// 如果大家想知道为何处理原生指针需要 `unsafe`,可以试着反注释以下代码
|
|
|
|
|
// 如果大家想知道为何处理裸指针需要 `unsafe`,可以试着反注释以下代码
|
|
|
|
|
// let message = get_str_at_location(1000, 10);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
@ -100,13 +100,13 @@ unsafe {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
使用 `*` 可以对原生指针进行解引用,由于该指针的内存安全性并没有任何保证,因此我们需要使用 `unsafe` 来包裹解引用的逻辑(切记,`unsafe` 语句块的范围一定要尽可能的小,具体原因在上一章节有讲)。
|
|
|
|
|
使用 `*` 可以对裸指针进行解引用,由于该指针的内存安全性并没有任何保证,因此我们需要使用 `unsafe` 来包裹解引用的逻辑(切记,`unsafe` 语句块的范围一定要尽可能的小,具体原因在上一章节有讲)。
|
|
|
|
|
|
|
|
|
|
以上代码另一个值得注意的点就是:除了使用 `as` 来显式的转换,我们还使用了隐式的转换方式 `let c: *const i32 = &a;`。在实际使用中,我们建议使用 `as` 来转换,因为这种显式的方式更有助于提醒用户:你在使用的指针是原生指针,需要小心。
|
|
|
|
|
以上代码另一个值得注意的点就是:除了使用 `as` 来显式的转换,我们还使用了隐式的转换方式 `let c: *const i32 = &a;`。在实际使用中,我们建议使用 `as` 来转换,因为这种显式的方式更有助于提醒用户:你在使用的指针是裸指针,需要小心。
|
|
|
|
|
|
|
|
|
|
#### 基于智能指针创建原生指针
|
|
|
|
|
#### 基于智能指针创建裸指针
|
|
|
|
|
|
|
|
|
|
还有一种创建原生指针的方式,那就是基于智能指针来创建:
|
|
|
|
|
还有一种创建裸指针的方式,那就是基于智能指针来创建:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
let a: Box<i32> = Box::new(10);
|
|
|
|
@ -118,9 +118,9 @@ let c: *const i32 = Box::into_raw(a);
|
|
|
|
|
|
|
|
|
|
#### 小结
|
|
|
|
|
|
|
|
|
|
像之前代码演示的那样,使用原生指针可以让我们创建两个可变指针都指向同一个数据,如果使用安全的 Rust,你是无法做到这一点的,违背了借用规则,编译器会对我们进行无情的阻止。因此原生指针可以绕过借用规则,但是由此带来的数据竞争问题,就需要大家自己来处理了,总之,需要小心!
|
|
|
|
|
像之前代码演示的那样,使用裸指针可以让我们创建两个可变指针都指向同一个数据,如果使用安全的 Rust,你是无法做到这一点的,违背了借用规则,编译器会对我们进行无情的阻止。因此裸指针可以绕过借用规则,但是由此带来的数据竞争问题,就需要大家自己来处理了,总之,需要小心!
|
|
|
|
|
|
|
|
|
|
既然这么危险,为何还要使用原生指针?除了之前提到的性能等原因,还有一个重要用途就是跟 `C` 语言的代码进行交互( FFI ),在讲解 FFI 之前,先来看看如何调用 unsafe 函数或方法。
|
|
|
|
|
既然这么危险,为何还要使用裸指针?除了之前提到的性能等原因,还有一个重要用途就是跟 `C` 语言的代码进行交互( FFI ),在讲解 FFI 之前,先来看看如何调用 unsafe 函数或方法。
|
|
|
|
|
|
|
|
|
|
## 调用 unsafe 函数或方法
|
|
|
|
|
|
|
|
|
@ -223,13 +223,13 @@ fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
|
|
|
|
|
|
|
|
|
|
相比安全实现,这段代码就显得没那么好理解了,甚至于我们还需要像 C 语言那样,通过指针地址的偏移去控制数组的分割。
|
|
|
|
|
|
|
|
|
|
- `as_mut_ptr` 会返回指向 `slice` 首地址的原生指针 `*mut i32`
|
|
|
|
|
- `as_mut_ptr` 会返回指向 `slice` 首地址的裸指针 `*mut i32`
|
|
|
|
|
- `slice::from_raw_parts_mut` 函数通过指针和长度来创建一个新的切片,简单来说,该切片的初始地址是 `ptr`,长度为 `mid`
|
|
|
|
|
- `ptr.add(mid)` 可以获取第二个切片的初始地址,由于切片中的元素是 `i32` 类型,每个元素都占用了 4 个字节的内存大小,因此我们不能简单的用 `ptr + mid` 来作为初始地址,而应该使用 `ptr + 4 * mid`,但是这种使用方式并不安全,因此 `.add` 方法是最佳选择
|
|
|
|
|
|
|
|
|
|
由于 `slice::from_raw_parts_mut` 使用原生指针作为参数,因此它是一个 `unsafe fn`,我们在使用它时,就必须用 `unsafe` 语句块进行包裹,类似的,`.add` 方法也是如此(还是那句话,不要将无关的代码包含在 `unsafe` 语句块中)。
|
|
|
|
|
由于 `slice::from_raw_parts_mut` 使用裸指针作为参数,因此它是一个 `unsafe fn`,我们在使用它时,就必须用 `unsafe` 语句块进行包裹,类似的,`.add` 方法也是如此(还是那句话,不要将无关的代码包含在 `unsafe` 语句块中)。
|
|
|
|
|
|
|
|
|
|
部分同学可能会有疑问,那这段代码我们怎么保证 `unsafe` 中使用的原生指针 `ptr` 和 `ptr.add(mid)` 是合法的呢?秘诀就在于 `assert!(mid <= len);` ,通过这个断言,我们保证了原生指针一定指向了 `slice` 切片中的某个元素,而不是一个莫名其妙的内存地址。
|
|
|
|
|
部分同学可能会有疑问,那这段代码我们怎么保证 `unsafe` 中使用的裸指针 `ptr` 和 `ptr.add(mid)` 是合法的呢?秘诀就在于 `assert!(mid <= len);` ,通过这个断言,我们保证了裸指针一定指向了 `slice` 切片中的某个元素,而不是一个莫名其妙的内存地址。
|
|
|
|
|
|
|
|
|
|
再回到我们的主题:**虽然 split_at_mut 使用了 `unsafe`,但我们无需将其声明为 `unsafe fn`**,这种情况下就是使用安全的抽象包裹 `unsafe` 代码,这里的 `unsafe` 使用是非常安全的,因为我们从合法数据中创建了的合法指针。
|
|
|
|
|
|
|
|
|
@ -315,7 +315,7 @@ pub extern "C" fn call_from_c() {
|
|
|
|
|
|
|
|
|
|
## 实现 unsafe 特征
|
|
|
|
|
|
|
|
|
|
说实话,`unsafe` 的特征确实不多见,如果大家还记得的话,我们在之前的 [Send 和 Sync](https://course.rs/advance/concurrency-with-threads/send-sync.html#为原生指针实现sync) 章节中实现过 `unsafe` 特征 `Send`。
|
|
|
|
|
说实话,`unsafe` 的特征确实不多见,如果大家还记得的话,我们在之前的 [Send 和 Sync](https://course.rs/advance/concurrency-with-threads/send-sync.html#为裸指针实现sync) 章节中实现过 `unsafe` 特征 `Send`。
|
|
|
|
|
|
|
|
|
|
之所以会有 `unsafe` 的特征,是因为该特征至少有一个方法包含有编译器无法验证的内容。`unsafe` 特征的声明很简单:
|
|
|
|
|
|
|
|
|
@ -333,7 +333,7 @@ fn main() {}
|
|
|
|
|
|
|
|
|
|
通过 `unsafe impl` 的使用,我们告诉编译器:相应的正确性由我们自己来保证。
|
|
|
|
|
|
|
|
|
|
再回到刚提到的 `Send` 特征,若我们的类型中的所有字段都实现了 `Send` 特征,那该类型也会自动实现 `Send`。但是如果我们想要为某个类型手动实现 `Send` ,例如为原生指针,那么就必须使用 `unsafe`,相关的代码在之前的链接中也有,大家可以移步查看。
|
|
|
|
|
再回到刚提到的 `Send` 特征,若我们的类型中的所有字段都实现了 `Send` 特征,那该类型也会自动实现 `Send`。但是如果我们想要为某个类型手动实现 `Send` ,例如为裸指针,那么就必须使用 `unsafe`,相关的代码在之前的链接中也有,大家可以移步查看。
|
|
|
|
|
|
|
|
|
|
总之,`Send` 特征标记为 `unsafe` 是因为 Rust 无法验证我们的类型是否能在线程间安全的传递,因此就需要通过 `unsafe` 来告诉编译器,它无需操心,剩下的交给我们自己来处理。
|
|
|
|
|
|
|
|
|
|