原始指针 raw pointer
Rust 支持原始指针, 原始指针与 C 语言中的指针是完全一样的.
与 C 语言一样, 可以用 *ptr
的方法对指针进行解引用, 用于访问指针指向的内存.
只读指针 const pointer
不能通过只读指针, 来修改指针指向的内存的值.
只读指针的形式是 *const T
, 相当于 C 语言中的 const T*
.
*const c_void
对应于 C 语言中的 const void*
, 可以代表指向任意类型的指针, 使用时需要显式地转型.
具体来说, 只读的原始指针分为三种:
*const T
, 指向元素T
的原始指针*const [T]
, 指向切片的原始指针*const [T; N]
, 指向数组的原始指针, 这里包含了数组中的元素类型T
以及元数的个数N
*const T
这个原始指针的用处要更广泛一些, 它可以与 C 语言中的 const T*
进行互操作, 是 ABI 兼容的.
先看一个示例程序:
use std::ffi::c_void; use std::ptr; fn main() { let x: i32 = 42; // 指向基础数据类型 x 的原始指针 let x_ptr: *const i32 = ptr::addr_of!(x); // 读取指针指向的内存所保存的值 unsafe { assert_eq!(*x_ptr, 42); } // 另一种方式来读取 unsafe { assert_eq!(x_ptr.read(), 42); } // 类型转换 let void_ptr: *const c_void = x_ptr.cast(); // 判断原始指针是否为空指针 assert!(!void_ptr.is_null()); let numbers = [1, 2, 3, 4, 5]; // 指向数组中第一个元素的原始指针 let first_num: *const i32 = numbers.as_ptr(); // 访问数组中的第一个元素 unsafe { assert_eq!(*first_num, 1); } // 访问数组中的第二个元素 unsafe { assert_eq!(*first_num.add(1), 2) } // 访问数组中的第四个元素 unsafe { assert_eq!(*first_num.offset(3), 4); } // 访问数组中的第五个元素 unsafe { assert_eq!(*first_num.byte_offset((4 * size_of::<i32>()) as isize), 5) } }
上面代码, 对应的内存操作如下图所示:
下面还列出了指针的常用运算.
is_null()
, 判断指针是否为空cast()
, 转换为指向另一种数据类型的指针cast_mut()
, 将不可变更指针转换为可变更指针addr()
, 得到指针的地址, 相当于x_ptr as usize
指针偏移运算:
offset()
byte_offset()
wrapping_offset()
wrapping_byte_offset()
add()
byte_add()
wrapping_add()
wrapping_byte_add()
sub()
byte_sub()
wrapping_sub()
wrapping_byte_sub()
两指针之间的关系:
offset_from()
byte_offset_from()
sub_ptr()
*const [T]
与 *const [T; N]
针对这两个指针的操作方式比较受限.
先看一个用例程序:
#![feature(array_ptr_get)] #![feature(slice_ptr_get)] use std::ptr; fn main() { // 在栈上的整数数组 let numbers: [i32; 5] = [1, 1, 2, 3, 5]; // 指向数组的原始指针 let num_ptr: *const [i32; 5] = &numbers as *const [i32; 5]; assert!(!num_ptr.is_null()); assert_eq!(size_of_val(&num_ptr), 8); unsafe { assert_eq!((*num_ptr)[0], 1); } unsafe { assert_eq!((*num_ptr)[4], 5); } // 得到 *const [T] 原始指针 let num_ptr_slice: *const [i32] = &numbers as *const [i32]; assert_eq!(size_of_val(&num_ptr_slice), 16); assert_eq!(num_ptr.as_slice(), num_ptr_slice); assert_eq!(num_ptr.as_ptr(), num_ptr_slice.as_ptr()); assert_eq!(num_ptr_slice.len(), 5); unsafe { assert_eq!((*num_ptr_slice)[0], 1); } unsafe { assert_eq!((*num_ptr_slice)[4], 5); } let num_slice: &[i32] = &numbers; assert_eq!(num_slice.len(), 5); // 从 *const [T] 转换成 &[T] let num_slice2: &[i32] = unsafe { &*num_ptr_slice }; // 这两个切片是完全相同的 assert!(ptr::eq(num_slice, num_slice2)); // 指向数组中第一个元素的原始指针 let num_first_ptr: *const i32 = numbers.as_ptr(); // 另一种方法, 得到指向数组中第一个元素的原始指针 let num_first_ptr2: *const i32 = num_ptr_slice.cast(); // 这两个原始指针指向同一个内存地址 assert!(ptr::addr_eq(num_first_ptr, num_first_ptr2)); unsafe { assert_eq!(*num_first_ptr, numbers[0]); } }
*const [T]
本身是一个切片引用, 它的内存布局与 &[T]
是一致的, 甚至可以进行互换.
*const [T; N]
占用的内存中, 只有一个指向原始数组的指针, 并不包含数组的元素个数, 元素个数是编译器处理的.
其内存操作如下图所示:
可变更指针 mutable pointer
可变更指针的形式是 *mut T
, 相当于 C 语言中的 T*
.
而 *mut c_void
相当于 C 语言中的 void*
, 可以代表指向任意类型的指针, 在使用时需要显式地转型.
所谓的可变更指针, 是可以通过该指针来修改指针所指向的内存的值.
与不可变更指针类型, 可变更指针也有三种形式:
*mut T
, 指向元素T
的原始指针*mut [T]
, 指向切片的原始指针*mut [T; N]
, 指向数组的原始指针, 这里包含了数组中的元素类型T
以及元数的个数N
*mut T
先看一个示例程序:
use std::ptr; fn main() { let mut x: i32 = 42; // 指向基础数据类型 x 的原始指针 let x_ptr: *mut i32 = ptr::addr_of_mut!(x); // 修改指针指向的内存所保存的值 unsafe { *x_ptr = 43; } // 读取数值 unsafe { assert_eq!(x_ptr.read(), 43); } // 类型转换 let i8_ptr: *mut i8 = x_ptr.cast(); #[cfg(target_endian = "little")] unsafe { assert_eq!(*i8_ptr, 43); } #[cfg(target_endian = "big")] unsafe { assert_eq!(*i8_ptr.add(3), 43); } let mut numbers = [1, 2, 3, 4, 5]; // 指向数组中第一个元素的原始指针 let first_num: *mut i32 = numbers.as_mut_ptr(); // 修改数组中的第一个元素 unsafe { *first_num = 2; } // 修改数组中的第二个元素 unsafe { *first_num.add(1) = 4; } // 修改数组中的第二个元素 unsafe { *first_num.wrapping_add(2) = 6; } // 修改数组中的第四个元素 unsafe { *first_num.offset(3) = 8; } // 修改数组中的第五个元素 unsafe { *first_num.byte_offset((4 * size_of::<i32>()) as isize) = 10; } assert_eq!(numbers, [2, 4, 6, 8, 10]); }
上面代码, 对应的内存操作如下图所示:
这里的, i8_ptr
比较有意思, 它只是指向了 x
的第一个字节, 如果是小端 (little endian) 的系统,
里面存放的数值恰好是 43
.
下面还列出了可变更指针的常用运算.
is_null()
, 判断指针是否为空cast()
, 转换为指向另一种数据类型的指针cast_const()
, 将可变更指针转为*const T
addr()
, 得到指针的地址, 相当于x_ptr as usize
指针偏移运算:
offset()
byte_offset()
wrapping_offset()
wrapping_byte_offset()
add()
byte_add()
wrapping_add()
wrapping_byte_add()
sub()
byte_sub()
wrapping_sub()
wrapping_byte_sub()
两指针之间的关系:
offset_from()
byte_offset_from()
sub_ptr()
*mut [T]
与 *mut [T; N]
先看一个用例程序:
#![feature(array_ptr_get)] #![feature(slice_ptr_get)] use std::ptr; fn main() { // 在栈上的整数数组 let mut numbers: [i32; 5] = [1, 2, 3, 4, 5]; // 指向数组的原始指针 let num_ptr: *mut [i32; 5] = ptr::addr_of_mut!(numbers); assert!(!num_ptr.is_null()); assert_eq!(size_of_val(&num_ptr), 8); unsafe { (*num_ptr)[0] = 2; (*num_ptr)[1] = 4; } // 得到 *mut [T] 原始指针 let num_ptr_slice: *mut [i32] = ptr::addr_of_mut!(numbers); assert_eq!(size_of_val(&num_ptr_slice), 16); assert_eq!(num_ptr.as_mut_slice(), num_ptr_slice); assert_eq!(num_ptr.as_mut_ptr(), num_ptr_slice.as_mut_ptr()); assert_eq!(num_ptr_slice.len(), 5); unsafe { (*num_ptr_slice)[2] = 6; (*num_ptr_slice)[3] = 8; } let num_slice: &mut [i32] = &mut numbers; assert_eq!(num_slice.len(), 5); num_slice[4] = 10; // 从 *mut [T] 转换成 &[T] let num_slice2: &mut [i32] = unsafe { &mut *num_ptr_slice }; // 这两个切片是完全相同的 assert!(ptr::eq(num_slice, num_slice2)); assert_eq!(numbers, [2, 4, 6, 8, 10]); // 指向数组中第一个元素的原始指针 let num_first_ptr: *mut i32 = numbers.as_mut_ptr(); // 另一种方法, 得到指向数组中第一个元素的原始指针 let num_first_ptr2: *mut i32 = num_ptr_slice.cast(); // 这两个原始指针指向同一个内存地址 assert!(ptr::addr_eq(num_first_ptr, num_first_ptr2)); unsafe { assert_eq!(*num_first_ptr, 2); } }
*mut [T]
本身是一个切片引用, 它的内存布局与 &mut [T]
是一致的, 甚至可以进行互换.
*mut [T; N]
占用的内存中, 只有一个指向原始数组的指针, 并不包含数组的元素个数, 元素个数是编译器处理的.
其内存操作如下图所示:
模拟 C++ 中的 const_cast::
前文有介绍过, Rust 中声明的变量默认都是只读的, 除非显式地声明为 mut
, 比如 let mut x = 42;
.
但有时候, 可能需要实现像 C++ 中的 const_cast()
那样的类型转换工作, 以方便在函数内部修改一个不可变变量的值.
以下代码片段演示了如何通过原始指针进行类型转换的操作:
fn main() { let x = 42; let x_ptr = &x as *const i32; let x_mut_ptr: *mut i32 = x_ptr.cast_mut(); unsafe { x_mut_ptr.write(43); } assert_eq!(x, 43); }
上面的示例中, 通过取得只读变量 x
的内存地址, 直接将新的值写入到该地址, 就可以强制修改它的值.