1. 指针的分类
在Rust里面指针大致可以分成两类:
-
普通指针(引用):&T/&mut T
保证内存块是安全有效的,也就是说
&T/&mut T满足内存块首地址内存对齐,内存块已经完成了初始化。在Rust中&T/&mut T是被绑定在某一内存块上,只能用于读写这一内存块。 -
裸指针(const T/ mut T)
裸指针是将内存块和类型系统相连接,裸指针代表了一个内存块,指示了内存块首地址,大小,对齐等属性。裸指针不保证这个内存块的有效性和安全性。所以很多操作要用到关键字
unsafe
1.1 裸指针的主要操作
- 将usize类型数值强制转换成裸指针类型,以此数值为首地址的内存块被转换为相应的类型。如果对这一转换后的内存块进行读写,可能造成内存安全问题。
- 在不同的裸指针类型之间进行强制转换,实质上完成了裸指针指向的内存块的类型强转,如果对这一转换后的内存块进行读写,可能造成内存安全问题。
*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可以用于修改指向的数据。
裸指针的常见用途包括:
- 与C语言库进行交互:裸指针可以与C语言的指针进行互操作,使得Rust可以方便地调用C语言库。
- 手动内存管理:裸指针可以用于手动分配和释放内存,以及执行其他低级内存操作。
在使用裸指针时,需要注意以下几点:
- 裸指针不会自动解引用,需要使用
*操作符进行手动解引用。 - 使用裸指针时需要确保内存安全,避免悬挂指针和野指针等问题。
- 裸指针的操作需要遵循Rust的类型安全和内存安全规则。
总之,裸指针是Rust中一种用于进行低级内存操作的工具,需要谨慎使用以确保内存安全和类型安全。裸指针的本质其实就是一个特殊的数据结构体。
我是蚂蚁背大象,文章对你有帮助给项目点个❤关注我GitHub:mxsm,文章有不正确的地方请您斧正,创建ISSUE提交PR~谢谢! Emal:mxsm@apache.com