本書github鏈接:inside-rust-std-library
前面章節(jié)參見:
深入RUST標(biāo)準(zhǔn)庫內(nèi)核(序言) - 簡書 (jianshu.com)
深入RUST標(biāo)準(zhǔn)庫內(nèi)核(一 概述) - 簡書 (jianshu.com)
RUST標(biāo)準(zhǔn)庫內(nèi)存相關(guān)模塊代碼分析
理解RUST程序的最關(guān)鍵點就是理解RUST內(nèi)存相關(guān)的標(biāo)準(zhǔn)庫代碼。內(nèi)存基本庫代碼給出了RUST最基本的一些規(guī)則如所有權(quán)轉(zhuǎn)移,借用,生命周期的奧秘。
RUST內(nèi)存相關(guān)主要包括:從內(nèi)存角度看RUST類型,內(nèi)存分配與釋放,內(nèi)存拷貝,置值,內(nèi)存地址操作等。
RUST類型系統(tǒng)的內(nèi)存布局
類型內(nèi)存布局是指類型的內(nèi)部變量在內(nèi)存布局中,內(nèi)存順序,內(nèi)存大小,內(nèi)存字節(jié)對齊等內(nèi)容。
對于GC機制的高級語言,類型內(nèi)存布局一般是交由編譯器決定的。程序員不需要關(guān)心。C/C++語言中類型只有固定的一種內(nèi)存布局排序方式和一經(jīng)配置即固定的對齊方式,編譯器不會對此進(jìn)行優(yōu)化,程序員可預(yù)測類型內(nèi)存布局。
RUST則不同,因為泛型,閉包,編譯器優(yōu)化的關(guān)系,類型內(nèi)存布局方式編譯器會根據(jù)需要對內(nèi)存布局做調(diào)整,對程序員來說類型的內(nèi)存布局是不可預(yù)測的,而在內(nèi)存操作中,類型內(nèi)存布局的一些信息是必須要使用的,所以,RUST提供了Layout內(nèi)存布局類型。此布局類型結(jié)構(gòu)是類型內(nèi)存操作的基礎(chǔ)。
Layout的數(shù)據(jù)結(jié)構(gòu)如下:
pub struct Layout {
// size of the requested block of memory, measured in bytes.
// 類型需占用的內(nèi)存大小,用字節(jié)數(shù)目表示
size_: usize,
// 按照此字節(jié)數(shù)目進(jìn)行類型內(nèi)存對齊, NonZeroUsize見代碼后面文字分析
align_: NonZeroUsize,
}
NonZeroUsize是一種非0值的usize, 這種類型主要應(yīng)用于不可取0的值,本結(jié)構(gòu)中, 字節(jié)對齊屬性變量不能被置0,所以用NonZeroUsize來確保安全性。如果用usize類型,那代碼中就可能會把0置給align_,導(dǎo)致bug產(chǎn)生。這是RUST的一個設(shè)計規(guī)則,所有的限制要在類型定義即顯性化,從而使bug在編譯中就被發(fā)現(xiàn)。
每一個RUST的類型都有自身獨特的內(nèi)存布局Layout。一種類型的Layout可以用intrinsic::<T>::size_of()及intrinsic::<T>::min_align_of()獲得的類型內(nèi)存大小和對齊來獲得。
RUST的內(nèi)存布局更詳細(xì)原理闡述請參考[RUST內(nèi)存布局] (https://doc.rust-lang.org/nomicon/data.html),
#[repr(transparent)]內(nèi)存布局模式
repr(transparent)用于僅包含一個成員變量的類型,該類型的內(nèi)存布局與成員變量類型的內(nèi)存布局完全一致。類型僅僅具備編譯階段的意義,在運行時,類型變量與其成員變量可以認(rèn)為是一個相同變量,可以相互無障礙類型轉(zhuǎn)換。使用repr(transparent)布局的類型基本是一種封裝結(jié)構(gòu)。
#[repr(packed)]內(nèi)存布局模式
強制類型成員變量以1字節(jié)對齊,此種結(jié)構(gòu)在協(xié)議分析和結(jié)構(gòu)化二進(jìn)制數(shù)據(jù)文件中經(jīng)常使用
#[repr(RUST)]內(nèi)存布局模式
默認(rèn)的布局方式,采用此種布局,RUST編譯器會根據(jù)情況來自行優(yōu)化內(nèi)存
#[repr(C)]內(nèi)存布局模式
采用C語言布局方式, 所有結(jié)構(gòu)變量按照聲明的順序在內(nèi)存排列。默認(rèn)4字節(jié)對齊。
RUST內(nèi)存的類型與函數(shù)庫體系
intrinsic 固有函數(shù)庫——內(nèi)存部分
intrinsics函數(shù)由編譯器內(nèi)置實現(xiàn),并提供給其他模塊使用,對于固有函數(shù),沒必要去關(guān)注如何實現(xiàn),重要的是了解其功能和如何使用,intrinsics內(nèi)存函數(shù)一般不由庫以外的代碼直接調(diào)用,而是由mem模塊和ptr模塊封裝后再提供給其他模塊。
intrinsics::drop_in_place<T:Sized?>(to_drop: * mut T) 在編譯器無法自動drop時, 手工調(diào)用此函數(shù)將內(nèi)存釋放
intrinsics::forget<T:Sized?> (_:T), 通知編譯器不回收forget的變量內(nèi)存
intrinsics::needs_drop<T>()->bool, 判斷T類型是否需要做drop操作,實現(xiàn)了Copy Trait的類型會返回false
intrinsics::transmute<T,U>(e:T)->U, 對于內(nèi)存布局相同的類型 T和U, 完成將類型T變量轉(zhuǎn)換為類型U變量
intrinsics::offset<T>(dst: *const T, offset: usize)->* const T, 相當(dāng)于C的類型指針加減計算
intrinsics::copy<T>(src:*const T, dst: *mut T, count:usize), 內(nèi)存拷貝, src和dst內(nèi)存可重疊, 類似c語言中的memmove
intrinsics::copy_no_overlapping<T>(src:*const T, dst: * mut T, count:usize), 內(nèi)存拷貝, src和dst內(nèi)存不重疊
intrinsics::write_bytes(dst: *mut T, val:u8, count:usize) , C語言的memset的RUST實現(xiàn)
intrinsics::size_of<T>()->usize 類型內(nèi)存空間字節(jié)大小
intrinsics::min_align_of<T>()->usize 返回類型對齊字節(jié)大小
intrinsics::size_of_val<T>(_:*const T)->usize返回指針指向的變量內(nèi)存空間字節(jié)大小
intrinsics::min_align_of_val<T>(_: * const T)->usize 返回指針指向的變量對齊字節(jié)大小
intrinsics::volatile_xxxx 通知編譯器不做內(nèi)存優(yōu)化的操作函數(shù),一般用于硬件訪問
intrinsics::volatile_copy_nonoverlapping_memory<T>(dst: *mut T, src: *const T, count: usize) 內(nèi)存拷貝
intrinsics::volatile_copy_memory<T>(dst: *mut T, src: *const T, count: usize) 功能類似C語言memmove
intrinsics::volatile_set_memory<T>(dst: *mut T, val: u8, count: usize) 功能類似C語言memset
intrinsics::volatile_load<T>(src: *const T) -> T讀取內(nèi)存或寄存器,字節(jié)對齊
intrinsics::volatile_store<T>(dst: *mut T, val: T)內(nèi)存或寄存器寫入,字節(jié)對齊
intrinsics::unaligned_volatile_load<T>(src: *const T) -> T 字節(jié)非對齊
intrinsics::unaligned_volatile_store<T>(dst: *mut T, val: T)字節(jié)非對齊
intrinsics::raw_eq<T>(a: &T, b: &T) -> bool 內(nèi)存比較,類似C語言memcmp
pub fn ptr_offset_from<T>(ptr: *const T, base: *const T) -> isize 基于類型T內(nèi)存布局的偏移量
pub fn ptr_guaranteed_eq<T>(ptr: *const T, other: *const T) -> bool 判斷兩個指針是否判斷, 相等返回ture, 不等返回false
pub fn ptr_guaranteed_ne<T>(ptr: *const T, other: *const T) -> bool 判斷兩個指針是否不等,不等返回true
ptr模塊初探
ptr模塊是RUST的對指針的實現(xiàn)模塊。相比于C指針內(nèi)存地址的簡單,RUST指針實現(xiàn)機制復(fù)雜的多,以滿足實現(xiàn)內(nèi)存安全的類型系統(tǒng)需求,并兼顧內(nèi)存使用效率和方便性。對內(nèi)存的操作需要對ptr的若干概念先做一個理解,本節(jié)主要基于intrinsics模塊的基礎(chǔ)對ptr模塊的一些類型結(jié)構(gòu)及函數(shù)做出分析,為下節(jié)的內(nèi)存類型和函數(shù)庫做一個基礎(chǔ)。
ptr模塊中原生指針具體實現(xiàn)
RUST的原生指針類型(*const T/*mut T)實質(zhì)是個數(shù)據(jù)結(jié)構(gòu)體,由兩個部分組成,第一個部分是一個內(nèi)存地址,第二個部分對這個內(nèi)存地址的限制性描述-元數(shù)據(jù)
//從下面結(jié)構(gòu)定義可以看到,*const T本質(zhì)就是PtrComponents<T>
pub(crate) union PtrRepr<T: ?Sized> {
pub(crate) const_ptr: *const T,
pub(crate) mut_ptr: *mut T,
pub(crate) components: PtrComponents<T>,
}
pub(crate) struct PtrComponents<T: ?Sized> {
//只能用*const (), * const T編譯器已經(jīng)默認(rèn)還帶有元數(shù)據(jù)。
pub(crate) data_address: *const (),
//不同類型指針的元數(shù)據(jù)
pub(crate) metadata: <T as Pointee>::Metadata,
}
//從下面Pointee的定義可以看到一個RUST的編程技巧,即Trait可以只用來實現(xiàn)對關(guān)聯(lián)類型的指定,Pointee這一Trait即只用來指定Metadata的類型。
pub trait Pointee {
/// The type for metadata in pointers and references to `Self`.
type Metadata: Copy + Send + Sync + Ord + Hash + Unpin;
}
//廋指針元數(shù)據(jù)是單元類型,即是空
pub trait Thin = Pointee<Metadata = ()>;
元數(shù)據(jù)的規(guī)則:
- 對于固定大小類型的指針(實現(xiàn)了
SizedTrait), RUST定義為廋指針(thin pointer),元數(shù)據(jù)大小為0,類型為(),這里要注意,RUST中數(shù)組也是固定大小的類型,運行中對數(shù)組下標(biāo)合法性的檢測,就是比較是否已經(jīng)越過了數(shù)組的內(nèi)存大小。 - 對于動態(tài)大小類型的指針(DST 類型),RUST定義為胖指針(fat pointer 或 wide pointer), 元數(shù)據(jù)為:
- 對于結(jié)構(gòu)類型,如果最后一個成員是動態(tài)大小類型(結(jié)構(gòu)的其他成員不允許為動態(tài)大小類型),則元數(shù)據(jù)為此動態(tài)大小類型
的元數(shù)據(jù) - 對于
str類型, 元數(shù)據(jù)是按字節(jié)計算的長度值,元數(shù)據(jù)類型是usize - 對于切片類型,例如
[T]類型,元數(shù)據(jù)是數(shù)組元素的數(shù)目值,元數(shù)據(jù)類型是usize - 對于trait對象,例如 dyn SomeTrait, 元數(shù)據(jù)是 [DynMetadata<Self>][DynMetadata](后面代碼解釋)
(例如:DynMetadata<dyn SomeTrait>)
隨著RUST的發(fā)展,有可能會根據(jù)需要引入新的元數(shù)據(jù)種類。
- 對于結(jié)構(gòu)類型,如果最后一個成員是動態(tài)大小類型(結(jié)構(gòu)的其他成員不允許為動態(tài)大小類型),則元數(shù)據(jù)為此動態(tài)大小類型
在標(biāo)準(zhǔn)庫代碼當(dāng)中沒有指針類型如何實現(xiàn)Pointee Trait的代碼,推測編譯器針對每個類型自動的實現(xiàn)了Pointee。
如下為rust編譯器代碼的一個摘錄
pub fn ptr_metadata_ty(&'tcx self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> {
// FIXME: should this normalize?
let tail = tcx.struct_tail_without_normalization(self);
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(_)
| ty::Foreign(..)
// 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
// 如果是固定類型,元數(shù)據(jù)是單元類型 tcx.types.unit,即為空
| ty::Tuple(..) => tcx.types.unit,
//對于字符串和切片類型,元數(shù)據(jù)為長度tcx.types.usize,這個是元素長度
ty::Str | ty::Slice(_) => tcx.types.usize,
//對于dyn Trait類型, 元數(shù)據(jù)從具體的DynMetadata獲取*
ty::Dynamic(..) => {
let dyn_metadata = tcx.lang_items().dyn_metadata().unwrap();
tcx.type_of(dyn_metadata).subst(tcx, &[tail.into()])
},
//以下類型不應(yīng)有元數(shù)據(jù)
ty::Projection(_)
| ty::Param(_)
| ty::Opaque(..)
| 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)
}
}
}
以上代碼中的中文注釋比較清晰的說明了編譯器對每一個類型(或類型指針)都實現(xiàn)了Pointee中元數(shù)據(jù)類型的獲取。
對于Trait對象的元數(shù)據(jù)的具體結(jié)構(gòu)定義見如下代碼:
//dyn Trait的元數(shù)據(jù)結(jié)構(gòu)
pub struct DynMetadata<Dyn: ?Sized> {
//堆中的函數(shù)VTTable變量的指針
vtable_ptr: &'static VTable,
//標(biāo)示結(jié)構(gòu)對Dyn的所有權(quán)關(guān)系
phantom: crate::marker::PhantomData<Dyn>,
}
struct VTable {
drop_in_place: fn(*mut ()),
size_of: usize,
align_of: usize,
}
PhantomData的含義英文如下:
Zero-sized type used to mark things that "act like" they own a T.
一個零占用的變量,使得結(jié)構(gòu)擁有了一個T類型的變量。在RUST中,這一用法常常為了表示生命周期關(guān)系,以作為安全性的一個判斷。
VTable 中包含4個成員,上面的結(jié)構(gòu)體僅列出了前三個,即指向?qū)崿F(xiàn)Trait的結(jié)構(gòu)的drop_in_place函數(shù)的指針; 結(jié)構(gòu)內(nèi)存占用字節(jié)大?。唤Y(jié)構(gòu)內(nèi)存對齊字節(jié)大??;VTable結(jié)構(gòu)后面的內(nèi)存為Trait的所有行為的函數(shù)指針數(shù)組。
ptr模塊函數(shù)
ptr::drop_in_place<T: ?Sized>(to_drop: *mut T) 此函數(shù)是編譯器實現(xiàn)的,用于不需要RUST自動drop時,由程序代碼調(diào)用以釋放內(nèi)存
ptr::metadata<T: ?Sized>(ptr: *const T) -> <T as Pointee>::Metadata用來返回原生指針的元數(shù)據(jù)
ptr::null<T>() -> *const T 返回0值的*const T,因為RUST安全代碼中指針不能為0,所以只能用這個函數(shù)獲得0值的* const T,這個函數(shù)也是RUST安全性的一個體現(xiàn)。
ptr::null_mut<T>()->*mut T 同上,只是返回的是*mut T
ptr::from_raw_parts<T: ?Sized>(data_address: *const (), metadata: <T as Pointee>::Metadata) -> *const T 從內(nèi)存地址和元數(shù)據(jù)生成原生指針
ptr::from_raw_parts_mut<T: ?Sized>(data_address: *mut (), metadata: <T as Pointee>::Metadata) -> *mut T 功能同上,形成可變指針
RUST指針類型轉(zhuǎn)換時,經(jīng)常使用以上兩個函數(shù)獲得需要的指針類型。
ptr::slice_from_raw_parts<T>(data: *const T, len: usize) -> *const [T]
ptr::slice_from_raw_parts_mut<T>(data: *mut T, len: usize) -> *mut [T] 由原生指針類型及切片長度獲得原生切片類型指針
ptr模塊的函數(shù)大部分邏輯都比較簡單。很多就是對intrinsic 函數(shù)做調(diào)用。*const T/* mut T被使用的場景如下:
1.需要做內(nèi)存布局相同的兩個類型之間的轉(zhuǎn)換,
2.對于數(shù)組或切片做頭指針偏移以獲取元素變量
3.由內(nèi)存頭指針生成數(shù)組或切片指針
4.內(nèi)存拷貝或內(nèi)存讀出/寫入
以上4個場景實際上都是編程中最基礎(chǔ)的操作。
由* const T生成*const [T]的函數(shù)代碼如下:
pub const fn slice_from_raw_parts<T>(data: *const T, len: usize) -> *const [T] {
//data.cast()將*const T轉(zhuǎn)換為 *const()
from_raw_parts(data.cast(), len)
}
pub const fn from_raw_parts<T: ?Sized>(
data_address: *const (),
metadata: <T as Pointee>::Metadata,
) -> *const T {
// SAFETY: Accessing the value from the `PtrRepr` union is safe since *const T
// and PtrComponents<T> have the same memory layouts. Only std can make this
// guarantee.
//由以下這個操作可以確認(rèn) * const T實質(zhì)是個結(jié)構(gòu)體。
unsafe { PtrRepr { components: PtrComponents { data_address, metadata } }.const_ptr }
}
*const T/*mut T/*const [T]/*mut [T] 若干方法
ptr::*const T::is_null(self)->bool
ptr::*mut T::is_null(self)->bool此函數(shù)判斷原生指針的地址值是否為0
ptr::*const T::cast<U>(self) -> *const U ,本質(zhì)上就是一個*const T as *const U。
ptr::*mut T::cast<U>(self)->*mut U cast函數(shù)主要完成不同類型的原生指針的互相轉(zhuǎn)換,這里需要程序員確保U與T的內(nèi)存布局一致,并保證指針的元數(shù)據(jù)也一致。
ptr::*const T::to_raw_parts(self) -> (*const (), <T as super::Pointee>::Metadata)
ptr::*mut T::to_raw_parts(self)->(* const (), <T as super::Pointee>::Metadata) 由原生指針獲得地址及元數(shù)據(jù)
ptr::*const T::as_ref<`a>(self) -> Option<&`a T> 將原生指針轉(zhuǎn)換為引用,因為*const T可能為零,所有需要轉(zhuǎn)換為Option<& `a T>類型,轉(zhuǎn)換的安全性由程序員保證,尤其注意滿足RUST對引用的安全要求。轉(zhuǎn)換后,數(shù)據(jù)進(jìn)入安全的RUST環(huán)境。
ptr::*mut T::as_ref<`a>(self)->Option<&`a T>
ptr::*mut T::as_mut<`a>(self)->Option<&`a mut T>同上,但轉(zhuǎn)化類型為 &mut T。
ptr::*const T::offset(self, count:isize)->* const T *mut T::offset(self, count:isize)->* mut T 實質(zhì)是intrinsics::offset的封裝
ptr::*const [T]::len()->usize 獲取切片元素數(shù)量
*const T及*mut T的方法的邏輯基本也都比較簡單,但涉及到較多的指針類型轉(zhuǎn)換,有時需要細(xì)致分析,舉例如下:
//該方法給* mut T置一個新值
pub fn set_ptr_value(mut self, val: *const u8) -> Self {
// 指針類型分析如下
// self: * mut T
// &mut self:&mut *mut T
// &mut self as *mut *const T: *mut *mut T as *mut *const T
// &mut self as *mut *const T as *mut *const u8: *mut *const T as * mut *const u8
let thin = &mut self as *mut *const T as *mut *const u8;
// 指針類型分析如下
//*thin: *(*mut *const u8)即mut *const u8
unsafe { *thin = val };
self
}
RUST引用&T的安全要求
- 引用的內(nèi)存地址必須是內(nèi)存2的冪次字節(jié)對齊的
- 引用的內(nèi)存內(nèi)容必須是初始化過的
舉例:
#[repr(packed)]
struct RefTest {a:u8, b:u16, c:u32}
fn main() {
let test = RefTest{a:1, b:2, c:3};
//下面代碼無法通過編譯,因為test.b 內(nèi)存字節(jié)位于奇數(shù),無法用于借用
let ref1 = &test.b
}