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
同上