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
1316fn 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