原始指针 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 Taddr(), 得到指针的地址, 相当于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 的内存地址, 直接将新的值写入到该地址, 就可以强制修改它的值.