Skip to content

Commit 60cfbce

Browse files
committed
🤖📝AI: 完善裸指针相关内容,增加创建方式、操作和最佳实践
1 parent 7e93f66 commit 60cfbce

1 file changed

Lines changed: 108 additions & 8 deletions

File tree

docs/notes/指针.md

Lines changed: 108 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
#
2-
3-
41
## 什么是裸指针
52

6-
在 Rust 中,裸指针(又叫原始指针,Raw Pointer)是一种不受 Rust 借用规则保护的指针类型。它们与 C 语言中的指针类似,只是没有经过 Rust 的安全检查和 borrow checker 的限制。使用裸指针需要特别小心,因为它们容易导致内存安全问题,如空指针、悬垂指针、非法内存访问等等。
3+
在 Rust 中,裸指针(又叫原始指针,Raw Pointer)是一种不受 Rust 借用规则保护的指针类型。它们与 C 语言中的指针类似,只是没有经过 Rust 的安全检查和 borrow checker 的限制。
4+
尽管 Rust 强调内存安全,但引入裸指针是为了支持一些无法在完全安全的 Rust 代码中完成的底层操作,例如与 C 语言代码进行互操作(FFI),或者在需要手动管理内存,实现特定的数据结构时。使用裸指针需要特别小心,因为它们容易导致内存安全问题,如空指针、悬垂指针、非法内存访问等等。
5+
6+
==裸指针有两种类型:`*const T``*mut T`==。前者是不可变的裸指针,后者是可变的裸指针。其中,`T` 是指针指向的类型,例如,`*const i32` 表示一个指向 `i32` 类型的不可变裸指针,`*mut String` 表示一个指向 `String` 类型的可变裸指针。与引用不同,裸指针:
77

8-
==裸指针有两种类型:`*const T``*mut T`==。前者是不可变的裸指针,后者是可变的裸指针。其中,`T` 是指针指向的类型,例如,`*const i32` 表示一个指向 `i32` 类型的不可变裸指针,`&mut String` 表示一个指向 `String` 类型的可变引用。
8+
- 允许同时拥有多个不可变指针和可变指针指向同一数据,或者同时拥有多个可变指针。
9+
- 不保证指向有效的内存(可能为 null,或指向已释放的内存)。
10+
- 不保证指向已初始化的数据。
11+
- 没有自动的生命周期管理和析构。
912

10-
要创建一个裸指针,可以使用取地址符号 `&` 并将其转换为具体的裸指针类型。例如:
13+
要创建一个裸指针,最常见的方式是从一个引用转换而来。例如:
1114

1215
```rust
1316
fn main() {
@@ -33,6 +36,91 @@ fn main() {
3336

3437
在上述代码示例中,我们使用 `unsafe` 块来解引用 `ptr` 指向的值,并将其打印输出。通过使用 `unsafe` 块,我们告诉 Rust 编译器这是一个不安全的操作,需要程序员自行承担安全风险和责任。
3538

39+
## 创建裸指针的更多方式
40+
41+
除了从引用转换,还有其他几种创建裸指针的方式:
42+
43+
1. **从智能指针转换**:例如,`Box<T>` 提供了 `into_raw` 方法,它会消耗 `Box<T>` 并返回一个裸指针,同时放弃对内存的管理。之后需要手动使用 `Box::from_raw` 来重新接管内存并正确释放。
44+
45+
```rust
46+
fn main() {
47+
let b = Box::new(5i32);
48+
let ptr: *mut i32 = Box::into_raw(b);
49+
// 此时 ptr 是一个裸指针,Box b 不再管理这块内存
50+
// ... 在 unsafe 块中使用 ptr ...
51+
unsafe {
52+
// 使用完毕后,需要将裸指针转换回 Box 以便 Rust 正确释放内存
53+
let _ = Box::from_raw(ptr);
54+
}
55+
}
56+
```
57+
58+
2. **创建空指针**:可以使用 `std::ptr::null()` 和 `std::ptr::null_mut()` 来创建不可变和可变的空指针。
59+
60+
```rust
61+
use std::ptr;
62+
63+
fn main() {
64+
let const_null_ptr: *const i32 = ptr::null();
65+
let mut_null_ptr: *mut i32 = ptr::null_mut();
66+
67+
assert!(const_null_ptr.is_null());
68+
assert!(mut_null_ptr.is_null());
69+
}
70+
```
71+
72+
解引用空指针是未定义行为。
73+
74+
3. **从整数地址创建**:可以将一个整数直接转换为裸指针。这是一种非常不安全的操作,通常只在与硬件交互或进行非常底层的编程时使用,因为无法保证该地址是有效的。
75+
76+
```rust
77+
fn main() {
78+
let address = 0x012345usize;
79+
let ptr = address as *const i32;
80+
// 对这个 ptr 的任何操作都极度不安全,因为我们不知道这个地址是否有效
81+
}
82+
```
83+
84+
## 使用裸指针的常见操作
85+
86+
使用裸指针时,大部分操作都需要在 `unsafe` 块中进行:
87+
88+
1. **解引用**:如前所述,使用 `*` 操作符来访问指针指向的数据。
89+
2. **指针运算**:可以使用 `offset` 方法进行指针的偏移计算。这个方法也是 `unsafe` 的,因为它不检查边界。
90+
91+
```rust
92+
fn main() {
93+
let arr = [10, 20, 30];
94+
let ptr: *const i32 = arr.as_ptr();
95+
96+
unsafe {
97+
println!("First element: {}", *ptr);
98+
println!("Second element: {}", *ptr.offset(1)); // 移动到下一个元素
99+
println!("Third element: {}", *ptr.offset(2)); // 移动到再下一个元素
100+
}
101+
}
102+
```
103+
104+
3. **读写数据**:可以使用 `read` 和 `write` 方法从指针指向的内存位置读取或写入数据。这些方法也是 `unsafe` 的,因为它们不保证指针有效或对齐。
105+
106+
- `ptr.read()`: 读取 `*ptr` 的值,但不会创建引用,适用于可能存在别名或未对齐的情况。
107+
- `ptr.write(value)`: 将 `value` 写入 `*ptr` 指向的内存,不会调用 `drop` 清理旧值。
108+
109+
```rust
110+
fn main() {
111+
let mut x = 10;
112+
let ptr_mut: *mut i32 = &mut x;
113+
114+
unsafe {
115+
println!("Original value: {}", ptr_mut.read()); // 读取值
116+
ptr_mut.write(20); // 写入新值
117+
println!("New value: {}", *ptr_mut); // 通过解引用读取
118+
}
119+
}
120+
```
121+
122+
4. **C 代码交互 (FFI)**:裸指针在与 C 语言或其他语言编写的库进行交互时非常关键,因为这些语言通常使用指针来传递数据。
123+
36124
## 在 Rust 中 一个指针所指向的内容与它本身的值有什么区别?
37125

38126
Rust 中,一个指针包含两个部分:指向的内容和指针本身的值。指针本身的值表示指针所指向的内容在内存中的地址,而指向的内容是存储在该地址上的数据。
@@ -44,7 +132,6 @@ fn main() {
44132

45133
下面是一个使用引用的示例:
46134

47-
48135
假设我们有一个指向 `x` 的裸指针 `ptr`,它将指向 `x` 存储在内存中的地址。我们可以通过解引用 `*ptr` 来获取指针所指向的内容,即 `x` 的值。下面是一个简单的示例:
49136

50137
```rust
@@ -78,4 +165,17 @@ fn main() {
78165
在上面的例子中,变量`ref_x`是一个指向`x`的引用(即:一个指向`x`变量的指针),它的值就是 `x` 在内存中的地址。可以通过 `*ref_x` 来访问 `ref_x` 指向的内容,即变量 `x` 的值。
79166

80167
需要注意的是,由于 Rust 的所有权机制,一旦一个变量的所有权被转移了,它所对应的内存区域就会被释放,因此它的指针也就失效了。
81-
因此,在 Rust 中需要非常注意指针的安全性和生命周期。**如果想要在多个地方共享数据,可以使用引用类型来传递数据的所有权而不是直接传递指针**。这样可以让编译器在编译时检查所有权是否正确转移,从而避免一些常见的内存安全问题。
168+
因此,在 Rust 中需要非常注意指针的安全性和生命周期。**如果想要在多个地方共享对数据的访问,可以使用引用(borrowing)。如果确实需要共享数据的所有权,可以使用像 `Rc<T>` (引用计数指针) 或 `Arc<T>` (原子引用计数指针,用于多线程) 这样的智能指针类型,而不是直接传递裸指针或试图通过简单引用转移所有权。**
169+
170+
## 使用裸指针的规则和最佳实践
171+
172+
尽管裸指针提供了更大的灵活性,但也带来了巨大的风险。在使用它们时,应遵循一些基本规则:
173+
174+
1. **最小化 `unsafe` 代码**:将 `unsafe` 块限制在绝对必要且尽可能小的范围内。将不安全的操作封装在安全的抽象之后是一个好主意。
175+
2. **程序员的责任**:编译器不会对裸指针的使用进行安全检查。因此,程序员必须自己确保:
176+
- 指针指向的是有效的、已初始化的内存。
177+
- 对于 `*mut T`,在写入时没有其他指针(无论是 `*const T` 还是 `*mut T`)同时访问或写入同一块内存(类似于 `&mut T` 的别名规则,但不由编译器强制)。`*const T` 则可以有多个别名。
178+
- 指针的生命周期是正确的,避免悬垂指针。
179+
3. **文档化不安全代码的假设**:如果编写了 `unsafe` 代码,务必清晰地文档化其依赖的假设和不变量,以便他人(或未来的你)能够理解其正确性。
180+
181+
裸指针是 Rust 中必要的“后门”,允许开发者在需要时绕过编译器的安全检查,以实现一些底层操作或与其他语言交互。然而,它们的使用应该非常谨慎,并且尽可能地被安全的抽象所封装。

0 commit comments

Comments
 (0)