Rust 裸指针剖析-进阶

898 阅读5分钟

1. 指针的分类

在Rust里面指针大致可以分成两类:

  • 普通指针(引用):&T/&mut T

    保证内存块是安全有效的,也就是说&T/&mut T满足内存块首地址内存对齐,内存块已经完成了初始化。在Rust中&T/&mut T是被绑定在某一内存块上,只能用于读写这一内存块。

  • 裸指针(const T/ mut T)

    裸指针是将内存块和类型系统相连接,裸指针代表了一个内存块,指示了内存块首地址,大小,对齐等属性。裸指针不保证这个内存块的有效性和安全性。所以很多操作要用到关键字 unsafe

1.1 裸指针的主要操作

  1. 将usize类型数值强制转换成裸指针类型,以此数值为首地址的内存块被转换为相应的类型。如果对这一转换后的内存块进行读写,可能造成内存安全问题。
  2. 在不同的裸指针类型之间进行强制转换,实质上完成了裸指针指向的内存块的类型强转,如果对这一转换后的内存块进行读写,可能造成内存安全问题。
  3. *const u8作为堆内存申请的内存块绑定变量

2. 指针的具体实现源码分析

裸指针(*const T/*mut T)和引用的本质数据结构体,由两部分组成:

  • 指针的首地址
  • 内存地址内容格式化描述,也就是内存地址的约束。直白的说就是这块内存可以存放什么内容,是String还是其他。也就是元数据
//裸指针的本质其实是PtrComponents<T>
#[repr(C)]
union PtrRepr<T: ?Sized> {
    const_ptr: *const T,
    mut_ptr: *mut T,
    components: PtrComponents<T>,
}

#[repr(C)]
struct PtrComponents<T: ?Sized> {
    //数据的地址
    data_address: *const (),
    //不同类型的指针元数据:也就是内存格式的描述
    metadata: <T as Pointee>::Metadata,
}

//这里Pointee只关联了类型,没有任何方法
pub trait Pointee {
    type Metadata: Copy + Send + Sync + Ord + Hash + Unpin;
}

对于静态大小的类型(实现 Sized traits)以及 extern 类型,指针被称为“瘦”:元数据的大小为零,其类型为 ()

pub trait Thin = Pointee<Metadata = ()>;

例子:

let i = 32;
let x = &i as *const i32;

上述代码称为瘦指针。

指向动态大小类型的指针被称为“flat”或“wide”,它们具有非零大小的元数据:

  • 对于最后一个字段是DST的结构,元数据是最后一个字段的元数据
  • 对于 str 类型,元数据是以字节为单位的长度,如 usize
  • 对于像 [T] 这样的切片类型,元数据是以item为单位的长度,如 usize
  • 对于像 dyn SomeTrait 这样的trait对象,元数据是 DynMetadata<Self> (例如, DynMetadata<dyn SomeTrait>
pub struct DynMetadata<Dyn: ?Sized> {
    vtable_ptr: &'static VTable,
    phantom: crate::marker::PhantomData<Dyn>,
}

在未来,Rust语言可能会获得具有不同指针元数据的新类型。

2.1 Pointee的实现

在官方文档上面有说明Rust会为每一个类型自动实现。看一下编译器的代码摘要:

    /// Returns the type of metadata for (potentially fat) pointers to this type,
    /// and a boolean signifying if this is conditional on this type being `Sized`.
    pub fn ptr_metadata_ty(
        self,
        tcx: TyCtxt<'tcx>,
        normalize: impl FnMut(Ty<'tcx>) -> Ty<'tcx>,
    ) -> (Ty<'tcx>, bool) {
        let tail = tcx.struct_tail_with_normalize(self, normalize, || {});
        match tail.kind() {
            // Sized types
            ty::Infer(ty::IntVar(_) | ty::FloatVar(_))
            | ty::Uint(_)
            | ty::Int(_)
            | ty::Bool
            | ty::Float(_)
            | ty::FnDef(..)
            | ty::FnPtr(_)
            | ty::RawPtr(..)
            | ty::Char
            | ty::Ref(..)
            | ty::Generator(..)
            | ty::GeneratorWitness(..)
            | ty::Array(..)
            | ty::Closure(..)
            | ty::Never
            | ty::Error(_)
            // Extern types have metadata = ().
            | ty::Foreign(..)
            // `dyn*` has no metadata
            | ty::Dynamic(_, _, DynKind::DynStar)
            // If returned by `struct_tail_without_normalization` this is a unit struct
            // without any fields, or not a struct, and therefore is Sized.
            | ty::Adt(..)
            // If returned by `struct_tail_without_normalization` this is the empty tuple,
            // a.k.a. unit type, which is Sized
            | ty::Tuple(..) => (tcx.types.unit, false), // 如果是固定类型,元数据是单元类型 tcx.types.unit,即为空
			
            //如果是str类型和切片类型元数据为长度tcx.types.usize,是元素长度
            ty::Str | ty::Slice(_) => (tcx.types.usize, false),
            //对于dyn Trait类型, 元数据从具体的DynMetadata获取
            ty::Dynamic(_, _, DynKind::Dyn) => {
                let dyn_metadata = tcx.require_lang_item(LangItem::DynMetadata, None);
                (tcx.type_of(dyn_metadata).instantiate(tcx, &[tail.into()]), false)
            },

            //类型参数只有大小有单位元数据,因此返回true以确保我们在确认过程中再次检查此项
            ty::Param(_) |  ty::Alias(..) => (tcx.types.unit, true),

            //其他的没有元数据
            ty::Infer(ty::TyVar(_))
            | ty::Bound(..)
            | ty::Placeholder(..)
            | ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => {
                bug!("`ptr_metadata_ty` applied to unexpected type: {:?} (tail = {:?})", self, tail)
            }
        }
    }

3. 裸指针创建

直接从已经初始化的变量创建指针:

&T as *const T;
&mut T as * mut T;

通过一些关联函数创建:

ptr::null<T>() -> *const T 
ptr::null_mut<T>()->*mut T 

ptr::invalid<T>(addr:usize)->*mut T 
ptr::invalid_mut<T>(addr:usize)->*mut T 

4. 总结

Rust中的裸指针(raw pointer)是一种低级指针,用于进行低级的内存操作。裸指针不遵循Rust的所有权和借用规则,因此在使用时需要格外小心,以避免引发内存安全问题。

裸指针有两种类型:*const T*mut T,分别表示不可变和可变的裸指针。*const T指向的数据不可变,而*mut T可以用于修改指向的数据。

裸指针的常见用途包括:

  1. 与C语言库进行交互:裸指针可以与C语言的指针进行互操作,使得Rust可以方便地调用C语言库。
  2. 手动内存管理:裸指针可以用于手动分配和释放内存,以及执行其他低级内存操作。

在使用裸指针时,需要注意以下几点:

  1. 裸指针不会自动解引用,需要使用*操作符进行手动解引用。
  2. 使用裸指针时需要确保内存安全,避免悬挂指针和野指针等问题。
  3. 裸指针的操作需要遵循Rust的类型安全和内存安全规则。

总之,裸指针是Rust中一种用于进行低级内存操作的工具,需要谨慎使用以确保内存安全和类型安全。裸指针的本质其实就是一个特殊的数据结构体。

我是蚂蚁背大象,文章对你有帮助给项目点个❤关注我GitHub:mxsm,文章有不正确的地方请您斧正,创建ISSUE提交PR~谢谢! Emal:mxsm@apache.com