深入RUST标准库内核—内存之Unique指针

本文摘自 inside-rust-std-library

mem模块函数库

mem::zeroed<T>() -> T 代码如下:


pub unsafe fn zeroed<T>() -> T {

    // 调用者必须确认T类型的变量可以取全零值

    unsafe {

        intrinsics::assert_zero_valid::<T>();

        MaybeUninit::zeroed().assume_init()

    }

}

mem::uninitialized<T>() -> T 用MaybeUnint::uninit获取一块未初始化内存,然后调用assume_init(), 此时内存彻底未初始化。


pub unsafe fn uninitialized<T>() -> T {

    // 调用者必须确认T类型的变量允许未初始化的任意值

    unsafe {

        intrinsics::assert_uninit_valid::<T>();

        MaybeUninit::uninit().assume_init()

    }

}

mem::take<T: Default>(dest: &mut T) -> T 将dest设置为默认内容(不改变所有权),用一个新变量返回dest的内容。

//使用take是一种照顾所有权的方式,直接用read会导致出现两份所有权,所以必须用replace清除原变量的所有权


pub fn take<T: Default>(dest: &mut T) -> T {

    //即mem::replace,见下文

    replace(dest, T::default())

}

mem::replace<T>(dest: &mut T, src: T) -> T 用src的内容赋值dest(不改变所有权),用一个新变量返回dest的内容。

//因为所有权的关系,RUST一般不使用ptr::write来直接赋值,而用mem::replace完成对所有权处理的内存赋值。


pub const fn replace<T>(dest: &mut T, src: T) -> T {

    unsafe {

        //因为要替换dest, 所以必须对dest原有变量的所有权做处理,因此先用read将*dest的所有权转移到T,交由调用者进行处理

        //注意,此时*dest可能是MaybeUninit或ManuallyDrop的对象,如调用者不清楚类型,那必须显性调用drop函数

        let result = ptr::read(dest);

        //ptr::write本身会导致src的所有权转移到dest,然后src被forget,这就处理了read()遗留的所有权问题

        ptr::write(dest, src);

        result

    }

}

mem::transmute_copy<T, U>(src: &T) -> U 新建类型U的变量,并把src的内容拷贝到U。调用者应保证T类型的内容与U一致


pub const unsafe fn transmute_copy<T, U>(src: &T) -> U {

    if align_of::<U>() > align_of::<T>() {

        // 如果两个类型字节对齐U 大于 T. 使用read_unaligned

        unsafe { ptr::read_unaligned(src as *const T as *const U) }

    } else {

        //用read即可完成

        unsafe { ptr::read(src as *const T as *const U) }

    }

}

mem::forget<T>(t:T) 通知RUST不做变量的drop操作


pub const fn forget<T>(t: T) {

    //没有使用intrinsic::forget, 实际上效果一致,这里应该是尽量规避用intrinsic函数

    let _ = ManuallyDrop::new(t);

}

mem::forget_unsized<T:Sized?> 对intrinsics::forget的封装

mem::size_of<T>()->usize/mem::min_align_of<T>()->usize/mem::size_of_val<T>(val:& T)->usize/mem::min_align_of_val<T>(val: &T)->usize/mem::needs_drop<T>()->bool 基本就是直接调用intrinsic模块的同名函数

mem::drop<T>(_x:T) 释放内存

ptr模块再探

ptr::read<T>(src: *const T) -> T 此函数用已有的类型复制出一个新的类型实体,对于不支持Copy Trait的类型,read函数是RUST实现未知类型变量的复制的一种方法,此函数作为内存函数take(), replace(), transmute_copy()的基础,底层使用intrisic::copy_no_overlapping支持,代码已经在MaybeUninit::assume_init_read那里已经分析过

ptr::read_unaligned<T>(src: *const T) -> T当数据结构中有未内存对齐的成员变量时,需要用此函数读取内容并转化为内存对齐的变量。否则会引发UB(undefined behaiver) 如下例:

/// 从字节数组中读一个usize的值:


    use std::mem;

    fn read_usize(x: &[u8]) -> usize {

        assert!(x.len() >= mem::size_of::<usize>());

        let ptr = x.as_ptr() as *const usize;

        //此处必须用ptr::read_unaligned,因为不确定字节是否对齐

        unsafe { ptr.read_unaligned() }

    }

例子中,为了从byte串中读取一个usize,需要用read_unaligned来获取值,不能象C语言那样通过指针类型转换直接获取值。

ptr::write<T>(dst: *mut T, src: T) 代码如下:


pub const unsafe fn write<T>(dst: *mut T, src: T) {

    unsafe {

        //浅拷贝

        copy_nonoverlapping(&src as *const T, dst, 1);

        //必须调用forget,这里所有权已经转移。不允许再对src做drop操作

        intrinsics::forget(src);

    }

}

write函数本质上就是一个所有权转移的操作。完成src到dst的浅拷贝,然后调用了forget(src), 这使得src的Drop不再被调用(也规避src类型如果有引用导致的重复释放问题)。从而将所有权转移到dst。此函数是mem::replace, mem::transmute_copy的基础。底层由intrisic:: copy_no_overlapping支持。

这个函数中,如果dst已经初始化过,那原dst变量的所有权将被丢失掉,有可能引发内存泄漏。

ptr::write_unaligned<T>(dst: *mut T, src: T) 与read_unaligned相对应。举例如下:


    #[repr(packed, C)]

    struct Packed {

        _padding: u8,

        unaligned: u32,

    }

    let mut packed: Packed = unsafe { std::mem::zeroed() };

    // Take the address of a 32-bit integer which is not aligned.

    // In contrast to `&packed.unaligned as *mut _`, this has no undefined behavior.

    // 对于结构中字节没有按照2幂次对齐的成员,要用addr_of_mut!宏来获得地址,无法用取引用的方式。

    let unaligned = std::ptr::addr_of_mut!(packed.unaligned);

    unsafe { std::ptr::write_unaligned(unaligned, 42) };

     assert_eq!({packed.unaligned}, 42); // `{...}` forces copying the field instead of creating a reference.

ptr::read_volatile<T>(src: *const T) -> T 是intrinsics::volatile_load的封装

ptr::write_volatile<T>(dst: *mut T, src:T) 是intrinsics::volatiel_store的封装

ptr::macro addr_of($place:expr) 因为用&获得引用必须是字节按照2的幂次对齐的地址,所以用这个宏获取非地址对齐的变量地址


pub macro addr_of($place:expr) {

    //关键字是&raw const,这个是RUST的原始引用语义,但目前还没有在官方做公开。

    //区别与&, &要求地址必须满足字节对齐和初始化,&raw 则没有这个问题

    &raw const $place

}

ptr::macro addr_of_mut($place:expr) 作用同上。


pub macro addr_of_mut($place:expr) {

    &raw mut $place

}

指针的通用函数请参考Rust库函数参考

NonNull 与MaybeUninit相关函数

NonNull<T>::as_uninit_ref<`a>(&self) -> &`a MaybeUninit<T> NonNull与MaybeUninit的引用基本就是直接转换的关系,一体双面


    pub unsafe fn as_uninit_ref<'a>(&self) -> &'a MaybeUninit<T> {

        // self.cast将NonNull<T>转换为NonNull<MaybeUninit<T>>

        //self.cast.as_ptr将NonNull<MaybeUninit<T>>转换为 *mut MaybeUninit<T>

        unsafe { &*self.cast().as_ptr() }

    }

NonNull<T>::as_uninit_mut<`a>(&self) -> &`a mut MaybeUninit<T>

NonNull<[T]>::as_uninit_slice<'a>(&self) -> &'a [MaybeUninit<T>]


    pub unsafe fn as_uninit_slice<'a>(&self) -> &'a [MaybeUninit<T>] {

        // 下面的函数调用ptr::slice_from_raw_parts

        unsafe { slice::from_raw_parts(self.cast().as_ptr(), self.len()) }

    }

NonNull<[T]>::as_uninit_slice_mut<'a>(&self) -> &'a mut [MaybeUninit<T>]

Unique

Unique类型结构定义如下


    #[repr(transparent)]

    pub struct Unique<T: ?Sized> {

        pointer: *const T,

        // NOTE: this marker has no consequences for variance, but is necessary

        // for dropck to understand that we logically own a `T`.

        //

        // For details, see:

        // https://github.com/rust-lang/rfcs/blob/master/text/0769-sound-generic-drop.md#phantom-data

        _marker: PhantomData<T>,

    }

和NonNull对比,Unique多了PhantomData类型变量。这个定义使得编译器知晓,Unique拥有了pointer指向的内存的所有权,NonNull没有这个特性。具备所有权后,Unique可以实现Send, Sync等Trait。因为获得了所有权,此块内存无法用于他处,这也是Unique的名字由来原因.

指针在被Unique封装前,必须保证是NonNull的

RUST用Allocator申请出来的内存的所有权用Unique做了绑定,使得内存进入了RUST的所有权和借用系统。

Unique模块的函数及代码与NonNull函数代码相类似,此处不分析。

Unique::cast<U>(self)->Unique<U> 类型转换,程序员应该保证T和U的内存布局相同

Unique::<T>::new(* mut T)->Option<Self> 此函数内部判断* mut T是否为0值

Unique::<T>::new_unchecked(* mut T)->Self 封装* mut T, 调用代码应该保证* mut T的安全性

Unique::as_ptr(self)->* mut T

Unique::as_ref(&self)->& T 因为Unique具备所有权,此处&T的生命周期与self相同,不必特别声明声明周期

Unique::as_mut(&mut self)->& mut T 同上

本作品采用《CC 协议》,转载必须注明作者和本文链接
Warren Ren
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!