跳过正文

16. 裸指针:raw pointer

·
Rust
目录
rust-lang - 这篇文章属于一个选集。
§ 16: 本文

Rust 提供两种裸指针类型:

  1. *mut T :可变裸指针,可以读写指向的内容;
  2. *const T :不可变裸指针,只能读而不能修改指向的内容;

裸指针的主要使用场景是 FFI,如 C 函数的指针类型需要裸指针来声明和传递参数等。

// libc 的 malloc 和 free 使用裸指针来管理内存
#[allow(unused_extern_crates)]
extern crate libc;
use std::mem;
unsafe {
    let my_num: *mut i32 = libc::malloc(mem::size_of::<i32>()) as *mut i32;
    if my_num.is_null() {
        panic!("failed to allocate memory");
    }
    libc::free(my_num as *mut libc::c_void);
}

通过对裸指针来保存对象,如 *ptr = data ,会先 ptr 指向的 old value。但通过裸指针、MaybeUninit<T>write() 方法或 std::ptr::write() 来写入新对象时,并不会 drop old value。

// 使用 MaybeUninit 来初始化 struct field
#[derive(Debug, PartialEq)]
pub struct Foo {
    name: String,
    list: Vec<u8>,
}

let foo = {
    let mut uninit: MaybeUninit<Foo> = MaybeUninit::uninit();
    // 转换为裸指针
    let ptr = uninit.as_mut_ptr();

    // Initializing the `name` field. Using `write` instead of assignment via `=` to
    // not call `drop` on the old, uninitialized value.
    unsafe { std::ptr::addr_of_mut!((*ptr).name).write("Bob".to_string()); }

    // Initializing the `list` field. If there is a panic here, then the `String` in
    // the `name` field leaks.
    unsafe { std::ptr::addr_of_mut!((*ptr).list).write(vec![0, 1, 2]); }

    unsafe { uninit.assume_init() }
};

裸指针可以是 unaligned 或 null。当解引用裸指针时,它必须是 non-null 和 aligned,这里的 aligned 是指指针的地址如 *const Tstd::mem::align_of::<T>() 的倍数。

unsafe
#

裸指针的 unsafe 体现在:

  1. 允许忽略借用规则:可以同时拥有同一个内存地址的可变和不可变指针,或者拥有指向同一个地址的多个可变指针;
  2. 不能保证总是指向有效的内存地址;
  3. 允许为空(null);
fn main() {
    let p;
    {
        let x = 5;
        p = &x as *const i32; // 合法
    }
    unsafe {
        // 未定义行为:p 悬垂了,但编译器不报错
        println!("{}", *p); // 6

        // 未定义行为:向只读对象写内容
        let pm = p as *mut i32;
        // 编译器不检查,不报错
        *pm = 6;
        println!("{}", *pm); // 6
    }
}

创建裸指针
#

从借用创建裸指针:

  • 可以使用安全代码创建裸指针,但只能在 unsafe block 中使用(解引用)裸指针;
  • *const T 和 *mut T 之间可以相互转换;
  • 裸指针不拥有值的所有权;
let mut num = 5;

// 有效的裸指针:可以同时创建的不可变和可变裸指针
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;

// 可以将 *const T 转换为 *mut T
let r2 = r1 as *mut i32;
let r2 = r1.cast_mut();

// 可能无效的裸指针
let address = 0x012345usize;
let r = address as *const i32;

let my_num: i32 = 10;

// 引用类型自动 type coercing 到 raw pointer
let my_num_ptr: *const i32 = &my_num;
let mut my_speed: i32 = 88;
let my_speed_ptr: *mut i32 = &mut my_speed;

let mut x = 10;
// as 运算符将借用转换为 raw pointer
let ptr_x = &mut x as *mut i32;

fn very_trustworthy(shared: &i32) {
    unsafe {
        let mutable = shared as *const i32 as *mut i32;
        *mutable = 20;
    }
}

// 只能在 unsafe block 中解引用裸指针
unsafe {
    println!("r1 is: {}", *r1);
    println!("r2 is: {}", *r2);
}

如果要获得 boxed 值的裸指针,需要先解引用 Box 再借用,这并不会转移 boxed 值的所有权。

  • Box::into_raw() 函数消耗 Box 的同时返回一个裸指针:
let my_num: Box<i32> = Box::new(10);
// &*my_num 的结果是 &i32,可以转换为 *const i32
let my_num_ptr: *const i32 = &* my_num;

let mut my_speed: Box<i32> = Box::new(88);
let my_speed_ptr: *mut i32 = &mut *my_speed;

let my_speed: Box<i32> = Box::new(88);
// 转移了 my_speed 的所有权。
let my_speed: *mut i32 = Box::into_raw(my_speed);
unsafe {
    drop(Box::from_raw(my_speed));
}

let y = Box::new(20);
let ptr_y = &*y as *const i32;
unsafe {
    *ptr_x += *ptr_y;
}
assert_eq!(x, 30);

使用宏 std::ptr::addr_of!()std::ptr::addr_of_mut!() 创建表达式值的裸指针。

其它创建裸指针的方式:

  1. 很多类型提供了 as_ptr/as_mut_ptr() 方法来返回裸指针;
  2. Owning 类型, 如 Box/Rc/Arc 的 into_raw/from_raw() 方法来生成裸指针或从裸指针创建对象;
  3. 使用 as 表达式;

&MaybeUninit<T>、&mut MaybeUninit<T>、&T、 &mut T、*const T、*mut T 的内存布局、大小、对齐都一致,可以使用 as 表达式相互转换:

// as 表达式支持的转换类型:https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.as.pointer

// 将裸指针转换为 usize
let ptr_num_cast = ptr as *const i32 as usize;

// 将裸指针转换为借用
// 这是因为 &T, &mut T, *const T 和 *mut T 内存布局是一致的,所以可以转换。
let ptr: *mut i32 = &mut 0;
let ref_casted = unsafe { &mut *ptr };

// 将 &mut T 转换为 &mut U:
let ptr = &mut 0;
let val_casts = unsafe { &mut *(ptr as *mut i32 as *mut u32) };

// 将 &[MaybeUninit<u8>] 转换为 *const MaybeUninit<u8>, 然后使用 as 转换为裸指针
let ma = [MaybeUninit<u8>; 32];
let mas: &[MaybeUninit<u8>] = ma;
let mac = unsafe {mas.as_ptr() as *const u8 as *mut u8}

packed struct 和未对齐的裸指针
#

默认情况下,struct 对象的 field 会通过 pading 来对齐。通过添加 packed attr,可以关闭 struct field padding 对齐机 制,这样 struct 的某个 field 可能是未对齐的。

对于未对齐的 field 不能进行借用(&T/&mut T),但是裸指针是可以未对齐的,有两种创建未对齐的裸指针方式:

  1. 通过 std::ptr::addr_of!()std::ptr::addr_of_mut!() 宏:
#[derive(Debug, Default, Copy, Clone)]
#[repr(C, packed)]
struct S {
    aligned: u8,
    unaligned: u32,
}
let s = S::default();
let p = std::ptr::addr_of!(s.unaligned);
  1. 或者通过 &raw const&raw mut 创建裸指针(不能使用 &T 或 &mut T,因为它要求表达式必须是对齐的):
#[derive(Debug, Default, Copy, Clone)]
#[repr(C, packed)]
struct S {
    aligned: u8,
    unaligned: u32,
}
let s = S::default();
let p = &raw const s.unaligned; // not allowed with coercion

null 类型裸指针
#

裸指针可以为 null:

  1. 创建 null 指针:
    1. std::ptr::null::<T>() 创建 *const T;
    2. std::ptr::null_mut::<T>() 创建 *mut T;
  2. 检查 null 指针:使用 is_null/as_ptr/as_mut_ptr() 方法;
fn option_to_raw<T>(opt: Option<&T>) -> *const T {
    match opt {
        None => std::ptr::null(),
        Some(r) => r as *const T
    }
}
assert!(!option_to_raw(Some(&("pea", "pod"))).is_null());
assert_eq!(option_to_raw::<i32>(None), std::ptr::null());

裸指针的一些限制
#

  1. 必须显式解引用,(*raw).field 或 (*raw).method(...)
  2. 裸指针不支持 Deref;
  3. 裸指针的比较运算,如 == 或 <, 比较的是指针地址, 而非指向的内容;
  4. 裸指针没有实现 Display, 但实现了 Debug 和 Pointer;
  5. 不支持裸指针的算术运算符, 但是可以使用库函数来进行运算;
  6. 裸指针没有实现 Send/Sync/Unpin, 不能跨线程或 async spawn 中使用;
let trucks = vec!["garbage truck", "dump truck", "moonstruck"];
let first: *const &str = &trucks[0];
let last: *const &str = &trucks[2];
assert_eq!(unsafe { last.offset_from(first) }, 2); // 返回偏移的元素数量
assert_eq!(unsafe { first.offset_from(last) }, -2);

// as 运算符支持将引用转换为 raw pointer(反过来不支持), 但是可能需要多次转换
&vec![42_u8] as *const String; // error: invalid conversion
&vec![42_u8] as *const Vec<u8> as *const String; // permitted

Rust 的 array/slice/vector 都是连续的内存地址块,每个元素占用固定大小的(std::mem::size_of<T>)内存:

fn offset<T>(ptr: *const T, count: isize) -> *const T where T: Sized
{
    let bytes_per_element = std::mem::size_of::<T>() as isize;
    let byte_offset = count * bytes_per_element;

    // 使用 ptr as isize 获得指针的实际值,然后进行数学运算,再将它转回 *const T
    (ptr as isize).checked_add(byte_offset).unwrap() as *const T
}

*const T 和 *mut T 方法
#

*const T 和 *mut T 是 Rust 基本类型,标准库为其定义了一些方法(std::ptr module 也提供了一些函数来操作裸指针):

  • 是否为 null : pub fn is_null(self) -> bool
  • 转换为 U 的指针 : pub const fn cast<U>(self) -> *const U
  • 返回指定 count 个对象的偏移指针 : pub const unsafe fn offset(self, count: isize) -> *const T
  • 计算相对于指定指针的对象数量 : pub const unsafe fn offset_from(self, origin: *const T) -> isize
  • 计算增加 count 个对象的偏移指针 : pub const unsafe fn add(self, count: usize) -> *const T
  • 计算减少 count 个对象的偏移指针 : pub const unsafe fn sub(self, count: usize) -> *const T
  • 读取指针内容,但是不 move self,返回 T 类型对象: pub const unsafe fn read(self) -> T
  • 拷贝 count * size_of<T>() 字节,src 和 dest 可以重叠 : pub const unsafe fn copy_to(self, dest: *mut T, count: usize)
  • 写入 T 值,但是不 read & drop 原来的值 : pub unsafe fn write(self, val: T)
  • 替换 T 值,但是不 read & drop 原来的值 : pub unsafe fn replace(self, src: T) -> T
  • 交换两个地址的值 : pub unsafe fn swap(self, with: *mut T)

从裸指针创建 T 对象:

  1. 使用 read() 方法;
  2. 使用 Box::from_raw() 方法;

*const T 实现的方法:

impl<T> *const T where T: ?Sized

pub fn is_null(self) -> bool
    let s: &str = "Follow the rabbit";
    let ptr: *const u8 = s.as_ptr();
    assert!(!ptr.is_null());

// 将 *const T 转换为 *const U
pub const fn cast<U>(self) -> *const U
// 将 *const T 转换为 *mut T
pub const fn cast_mut(self) -> *mut T
pub fn with_metadata_of<U>(self, meta: *const U) -> *const U where U: ?Sized

// 返回 *const T 指针的整型值,后续可以对它进行数学运算
pub fn addr(self) -> usize
pub fn expose_addr(self) -> usize
// 反过来,使用指定的 addr 创建一个裸指针
pub fn with_addr(self, addr: usize) -> *const T
// 使用指定的 f 将自身地址映射到新地址
pub fn map_addr(self, f: impl FnOnce(usize) -> usize) -> *const T

// 返回自身指针和 Metadata
pub fn to_raw_parts(self) -> (*const (), <T as Pointee>::Metadata)

// 将裸指针转换为借用,如果指针是 null 则返回 None
pub unsafe fn as_ref<'a>(self) -> Option<&'a T>
pub unsafe fn as_uninit_ref<'a>(self) -> Option<&'a MaybeUninit<T>>
    let ptr: *const u8 = &10u8 as *const u8;
    unsafe {
        if let Some(val_back) = ptr.as_ref() {
            println!("We got back the value: {val_back}!");
        }
    }

// 计算经过 count 个对象后的 offset 地址,offset 起始地址和加了 count*size 的结束地址都必须位于已分配对象的内存范围内,否则是 UB。
pub const unsafe fn offset(self, count: isize) -> *const T
    let s: &str = "123";
    let ptr: *const u8 = s.as_ptr();
    unsafe {
        println!("{}", *ptr.offset(1) as char);
        println!("{}", *ptr.offset(2) as char);
    }
// 和 offset() 类似,但是使用字节数偏移
pub const unsafe fn byte_offset(self, count: isize) -> *const T

// wrapping_offset 和 offset 相比,不要求起始和结束地址都位于已分配对象的内存范围内。
pub const fn wrapping_offset(self, count: isize) -> *const T
pub const fn wrapping_byte_offset(self, count: isize) -> *const T
    let data = [1u8, 2, 3, 4, 5];
    let mut ptr: *const u8 = data.as_ptr();
    let step = 2;
    let end_rounded_up = ptr.wrapping_offset(6);
    // 打印 "1, 3, 5, "
    while ptr != end_rounded_up {
        unsafe {
            print!("{}, ", *ptr);
        }
        ptr = ptr.wrapping_offset(step);
    }

pub fn mask(self, mask: usize) -> *const T

// 返回 self 和 origin 之间的元素数量
pub const unsafe fn offset_from(self, origin: *const T) -> isize
// 返回 byte 数量
pub const unsafe fn byte_offset_from<U>(self, origin: *const U) -> isize where U: ?Sized,
    let a = [0; 5];
    let ptr1: *const i32 = &a[1];
    let ptr2: *const i32 = &a[3];
    unsafe {
        assert_eq!(ptr2.offset_from(ptr1), 2);
        assert_eq!(ptr1.offset_from(ptr2), -2);
        assert_eq!(ptr1.offset(2), ptr2);
        assert_eq!(ptr2.offset(-2), ptr1);
    }

// add/sub/sub_ptr() 的关系:
// ptr.sub_ptr(origin) == count
// origin.add(count) == ptr
// ptr.sub(count) == origin
pub unsafe fn sub_ptr(self, origin: *const T) -> usize // 返回两个指针之间的元素数量

pub fn guaranteed_eq(self, other: *const T) -> Option<bool>
pub fn guaranteed_ne(self, other: *const T) -> Option<bool>

// 返回增加 count 个元素后新的地址
pub const unsafe fn add(self, count: usize) -> *const T
pub const unsafe fn byte_add(self, count: usize) -> *const T
    let s: &str = "123";
    let ptr: *const u8 = s.as_ptr();
    unsafe {
        println!("{}", *ptr.add(1) as char);
        println!("{}", *ptr.add(2) as char);
    }

// Calculates the offset from a pointer (convenience for .offset((count as isize).wrapping_neg())).
// count is in units of T; e.g., a count of 3 represents a pointer offset of 3 * size_of::<T>() bytes.
pub const unsafe fn sub(self, count: usize) -> *const T
pub const unsafe fn byte_sub(self, count: usize) -> *const T

pub const fn wrapping_add(self, count: usize) -> *const T
pub const fn wrapping_byte_add(self, count: usize) -> *const T
pub const fn wrapping_sub(self, count: usize) -> *const T
pub const fn wrapping_byte_sub(self, count: usize) -> *const T

// Reads the value from self without moving it. This leaves the memory in self unchanged. See
// ptr::read for safety concerns and examples.
pub const unsafe fn read(self) -> T
pub unsafe fn read_volatile(self) -> T
pub const unsafe fn read_unaligned(self) -> T

// Copies count * size_of<T> bytes from self to dest. The source and destination may
// overlap. NOTE: this has the same argument order as ptr::copy. See ptr::copy for safety concerns and examples.
pub const unsafe fn copy_to(self, dest: *mut T, count: usize)
pub const unsafe fn copy_to_nonoverlapping(self, dest: *mut T, count: usize)

pub fn align_offset(self, align: usize) -> usize
pub fn is_aligned(self) -> bool
pub fn is_aligned_to(self, align: usize) -> bool

*mut T 类型是在 *const T 的方法基础上,增加了一些 write 相关的方法:

impl<T> *mut T where T: ?Sized

// ...
pub unsafe fn as_mut<'a>(self) -> Option<&'a mut T>
pub unsafe fn as_uninit_mut<'a>(self) -> Option<&'a mut MaybeUninit<T>>
// ...

// Executes the destructor (if any) of the pointed-to value. See ptr::drop_in_place for safety concerns and examples.
pub unsafe fn drop_in_place(self)

// Overwrites a memory location with the given value without reading or dropping the old value. See
// ptr::write for safety concerns and examples.
pub unsafe fn write(self, val: T)
pub unsafe fn write_bytes(self, val: u8, count: usize)
pub unsafe fn write_volatile(self, val: T)
pub unsafe fn write_unaligned(self, val: T)

pub unsafe fn replace(self, src: T) -> T
pub unsafe fn swap(self, with: *mut T)
pub fn align_offset(self, align: usize) -> usize
pub fn is_aligned(self) -> bool
pub fn is_aligned_to(self, align: usize) -> bool

std::ptr
#

std::ptr module 提供操作 raw pointer(*const T, *mut T) 的函数。

通过 *const T*mut T 来存取 T 值,大小一般是 std::mem::size_of::<T>() 字节。

packed struct:默认情况下,struct field 会被 pading 对齐。通过添加 packed attr,可以关闭 padding 对齐。

#[derive(Debug, Default, Copy, Clone)]
#[repr(C, packed)]
struct S {
    aligned: u8,
    unaligned: u32,
}

let s = S::default();

// not allowed with coercion
let p = std::ptr::addr_of!(s.unaligned);

std::mem::offset_of!() 返回 struct field 或 enum variant 的偏移量。

std::ptr::addr_of!(expr)/addr_of_mut!(expr) 宏返回 expr 的 raw pointer,也可以用于未对齐的 raw pointer:

  • 这两个宏等效于 &raw const expr,和 &mut raw const expr
  • 相比 &mut expr as * mut _ 的好处是,省去创建了一个引用。
  • 未对齐的 field 不能创建引用。

raw pointer 和引用都是指针,包括两部分:

  1. data pointer:指向 value 内存地址的指针;
  2. 可选的 metadata 指针;

对于编译时可知的固定大小类型(实现 Sized trait)或 extern 类型的指针,是 thin 指针,metadata 是零内存的 () 类型,所以 thin 指针 是只占用一个机器字 usize 的变量。

对于动态大小类型(DST),它的指针是 fat 指针 ,这时 metadata 非空,如 *const [u8]*mut dyn std::io::Write

  • 对于最后一个 field 是 DST 的 struct,struct 的 metadata 是最后一个 field 的 metadata;
  • 对于 str 类型,metadata 是字符串字节数量(usize);
  • 对于 slice 类型,metadata 是元素的数量(usize);
  • 对于 trait object,如 dyn SomeTrait,metadata 是 DynMetadata<Self> (如 DynMetadata<dyn SomeTrait>)

std::ptr::Pointee trait 为任意指针提供 metadata type 信息, Rust 为所有类型实现了该 trait。其中的 Metadata 关联类型可能是 () 或 usize 或 DynMetadata<_> 类型;

  • (): 零大小,对应没有 Metadata 的 thin 指针;
  • usize:对应 byte 数量(如 &str)或 item 数量(如 [T]);
  • DynMetadata: 对应 trait object,也是个 usize 大小的指针;

std::ptr::to_raw_parts() 返回对象的 data pointer 和 Pointee 对象。

std::ptr::metadata() 方法返回对象的 Metadata 类型对象;

std::ptr::from_raw_parts()/from_raw_parts_mut() 使用 data pointer 和 Metadata 类型对象创建 raw pointer:

pub trait Pointee {
    // Metadata 类型可能是:() 或 usize 或 DynMetadata<Dyn: ?Sized>
    type Metadata: Copy + Send + Sync + Ord + Hash + Unpin;
}

pub struct DynMetadata<Dyn> where Dyn: ?Sized

// Decompose a (possibly wide) pointer into its data pointer and metadata components.
pub fn to_raw_parts(self) -> (*const (), <T as Pointee>::Metadata)

// Forms a (possibly-wide) raw pointer from a data pointer and metadata.
pub fn from_raw_parts<T>(data_pointer: *const (), metadata: <T as Pointee>::Metadata) -> *const T where T: ?Sized,

std::ptr::DynMetadatatrait object 的 metadata,vtable(virtual call table)的指针,指向 trait object 对应的具体值类型的实现,vtable 包括:

  • type size
  • type alignment
  • a pointer to the type’s drop_in_place impl (may be a no-op for plain-old-data)
  • pointers to all the methods for the type’s implementation of the trait

Rust 的 type coercion 机制为引用和 raw pointer 提供了隐式自动转换:

  1. &mut T to &T <- 可变引用可以转换为不可变引用
  2. *mut T to *const T <– 可变 raw pointer 可以转换为不可变 raw pointer
  3. &T to *const T <– 不可变引用 可以转换为 不可变 raw pointer
  4. &mut T to *mut T <– 可变引用 可以转换为 可变 raw pointer

可以使用 as 运算符根据 type coercion 等规则来进行显式转换:

  • 规则:https://doc.rust-lang.org/reference/expressions/operator-expr.html#type-cast-expressions
// &mut T -> &T
let r: &i32 = &mut 1i32;

// &T -> *const T
let rp: *const i32 = &1i32;

// &mut T -> *const T 或 *mut T
let r: *const i32 = &mut 1i32;
let r: *mut i32 = &mut 1i32;

// *mut T -> *const T
// 对支持 type coercion 的转换类型,也可以使用 as 运算符显式转换.
let rp: *const i32 = &mut 1i32 as *mut i32 as *const i32;

还有些 unsafe 的转换规则,不在上面文档中, 主要是利用 &MaybeUninit<T>、&mut MaybeUninit<T>、&T、 &mut T、*const T、*mut T 的内存布局、大小、对齐都一致,可以使用 as 表达式相互转换:

// as 表达式支持的转换类型:https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.as.pointer

// 将裸指针转换为 usize
let ptr_num_cast = ptr as *const i32 as usize;

// 将裸指针转换为借用
// 这是因为 &T, &mut T, *const T 和 *mut T 内存布局是一致的,所以可以转换。
let ptr: *mut i32 = &mut 0;
let ref_casted = unsafe { &mut *ptr };

// 将 &mut T 转换为 &mut U:
let ptr = &mut 0;
let val_casts = unsafe { &mut *(ptr as *mut i32 as *mut u32) };

// 将 &[MaybeUninit<u8>] 转换为 *const MaybeUninit<u8>, 然后使用 as 转换为裸指针
let ma = [MaybeUninit<u8>; 32];
let mas: &[MaybeUninit<u8>] = ma;
let mac = unsafe {mas.as_ptr() as *const u8 as *mut u8}

pub const unsafe fn read<T>(src: *const T) -> T : 创建 src 的一个 bitwise copy 值(返回的 T 和 src 指向同一个内存区域),而不管 T 是否实现了 Copy,不影响和移动 src 内容。如果 T 未实现 Copy,则 src 和返回的 T 由于指向同一个内存区域, 在返回的 T 对象被 drop 后,后续 如果再释放 src 会出现多重释放的问题。

pub unsafe fn write<T>(dst: *mut T, src: T) :不会 drop dst(有可能导致 dst 内存泄露),将 src 所有权转 移到 dst。

对比: *src = value; 会 Drop src 的值,而 std::ptr::write(dst, src) 在将 src 转移到 dst 中的同时不 drop dst 的值。这点对于通过 raw pointer 操作 MybeUninit 的内存区域很重要(因为未初始化,所以 drop UB)。

let x = 12;
let y = &x as *const i32;
unsafe {
    assert_eq!(std::ptr::read(y), 12);
}

fn swap<T>(a: &mut T, b: &mut T) {
    unsafe {
        // Create a bitwise copy of the value at `a` in `tmp`.
        let tmp = std::ptr::read(a);

        // Exiting at this point (either by explicitly returning or by calling a
        // function which panics) would cause the value in `tmp` to be dropped while
        // the same value is still referenced by `a`. This could trigger undefined
        // behavior if `T` is not `Copy`.

        // Create a bitwise copy of the value at `b` in `a`. This is safe because
        // mutable references cannot alias.
        std::ptr::copy_nonoverlapping(b, a, 1);

        // As above, exiting here could trigger undefined behavior because the same
        // value is referenced by `a` and `b`.

        // Move `tmp` into `b`.
        std::ptr::write(b, tmp);

        // `tmp` has been moved (`write` takes ownership of its second argument), so
        // nothing is dropped implicitly here.

        // tmp 和 a 虽然指向同一个内存区域,但由于 tmp 已经被 write() 转移所有权,所以这里不会释放 tmp,不会有双重释放的问题。
    }
}

let mut foo = "foo".to_owned();
let mut bar = "bar".to_owned();
swap(&mut foo, &mut bar);
assert_eq!(foo, "bar");
assert_eq!(bar, "foo");

let mut s = String::from("foo");
unsafe {
    // String 未实现 Copy,所以 s2 和 s 共享相同的内存区域
    let mut s2: String = ptr::read(&s);
    assert_eq!(s2, "foo");

    // 向 s2 赋值时,会 Drop 对应内存区域的原始值,所以 s 不能再使用(因其内存已经被释放)
    s2 = String::default();
    assert_eq!(s2, "");

    // Assigning to `s` would cause the old value to be dropped again, resulting in undefined behavior.
    // s = String::from("bar"); // ERROR

    // ptr::write() 覆盖 s 中的值但不 Drop 它,所以 OK。
    ptr::write(&mut s, String::from("bar"));
}
assert_eq!(s, "bar");

异常的情况:String 没有实现 Copy,所以 read() 的 bitwise-copy 机制会将 s 和 spr 都指向同一个内存区域,会导致双重释放,从而导致运 行时出现 abort 终止:

fn main() {
    let mut s = "abcd".to_string();
    let sp = &mut s as *mut String;
    let mut spr = unsafe {std::ptr::read(sp)};
    spr.push_str(" test");
    println!("s {}, spr {}", s, spr);
}

// 报错:
// Running `target/debug/ptr`
// s abcd, spr abcd test
// ptr(19630,0x1ffb75f00) malloc: *** error for object 0x6000012d4050: pointer being freed was not allocated
// ptr(19630,0x1ffb75f00) malloc: *** set a breakpoint in malloc_error_break to debug
// Abort trap: 6

std::ptr module 提供的其它操作 raw pointer 的函数:

  • copy<T>(src, dst, count): src、dst 内存可以重叠,如果 T 未实现 Copy,则是 bit-copy;
  • copy_nonoverlapping<T>(src, dst, count): src、dst 是不能重叠的;
  • drop_in_place<T>(to_drop: *mut T): 调用 T 的 drop 来丢弃它,特别适合于 unsized 的 trait object 等对象;
  • std::ptr::null()/null_mut(): 创建一个 null 值的 *const T*mut T,等效于:
    • MaybeUninit::<*const T>::zeroed().assume_init()
    • MaybeUninit::<*mut T>::zeroed().assume_init()
  • std::ptr::read(src: *const T)-> T: 从 src 读取 T 值(bit-copy),但是不移动它,src 内存不变;
  • pub const unsafe fn write_bytes<T>(dst: *mut T, val: u8, count: usize):将 dst 开始的 count * size_of::<T>() 字节设置为 val。

copy 的 count 用于指定从 src 拷贝 count * size_of::<T>() 字节到 dst。

// Copies count * size_of::<T>() bytes from src to dst. The source and destination may overlap.
// Like read, copy creates a bitwise copy of T, regardless of whether T is Copy.
// If T is not Copy, using both the values in the region beginning at *src and the region beginning
// at *dst can violate memory safety.
pub const unsafe fn copy<T>(src: *const T, dst: *mut T, count: usize)
  unsafe fn from_buf_raw<T>(ptr: *const T, elts: usize) -> Vec<T> {
      let mut dst = Vec::with_capacity(elts);

      // SAFETY: Our precondition ensures the source is aligned and valid,
      // and `Vec::with_capacity` ensures that we have usable space to write them.
      ptr::copy(ptr, dst.as_mut_ptr(), elts);

      // SAFETY: We created it with this much capacity earlier,
      // and the previous `copy` has initialized these elements.
      dst.set_len(elts);
      dst
  }

// Copies count * size_of::<T>() bytes from src to dst. The source and destination must not overlap.
pub const unsafe fn copy_nonoverlapping<T>( src: *const T, dst: *mut T, count: usize,)

let p: *const i32 = ptr::null();
assert!(p.is_null());
assert_eq!(p as usize, 0); // this pointer has the address 0

let mut vec = vec![0u32; 4];
unsafe {
    let vec_ptr = vec.as_mut_ptr();
    ptr::write_bytes(vec_ptr, 0xfe, 2);
}
assert_eq!(vec, [0xfefefefe, 0xfefefefe, 0, 0]);
rust-lang - 这篇文章属于一个选集。
§ 16: 本文

相关文章

1. 标识符和注释:identify/comment
·
Rust
Rust 标识符介绍
10. 泛型和特性:generic/trait
·
Rust
Rust 泛型和特性
11. 类型协变:type coercion
·
Rust
Rust 高级话题:子类型和类型协变
12. 迭代器:iterator
·
Rust
Rust 迭代器