導讀
Rust作為一門學習曲線十分陡峭的語言,掌握其核心基礎數(shù)據(jù)結(jié)構(gòu)的內(nèi)存分布對學習Rust會有很大的幫助,本文由淺入深仔細介紹了Rust的各個數(shù)據(jù)結(jié)構(gòu)在內(nèi)存中的分布情況。
先決條件 Prerequisite
-
我們本文的機器預設是32位的(主要為了畫圖可以精簡一點),所有和位相關的數(shù)據(jù)結(jié)構(gòu)均會用上標標記(即這些數(shù)據(jù)結(jié)構(gòu)占用的是1 machine word)。例如:
-
數(shù)據(jù)結(jié)構(gòu)基本單位示圖:
藍色的框框代表1個byte,綠色的框框pointer下的(1|2|3|4)代表pointer在32位機器上rust是4個byte,他們整體被框在綠色框框中代表一個pointer。
?
一、基本類型
這些數(shù)據(jù)結(jié)構(gòu)Rust分配的時候都是在棧上的。
?
1.1 Stack棧 vs Heap堆
我們只提煉一些最基本的區(qū)別概要,更多的細節(jié)可以看這篇文章[1]有比較好的解釋。
棧特點:
-
分配快 -
大小受限
-
分配慢 -
大小不受限 - ?
二、元組 Tuple
?
let a:(char, u8, i32) = ('a', 7, 354);
size_of::<(char, u8, i32)>(); // 打印結(jié)果 12
align_of::<(char, u8, i32)>(); // 打印結(jié)果 4
三、引用 Reference
let a: i8 = 6;
let b : &i8 = &a;
a?是一個i8,b是一個指向?a?的reference,我們可以看下他倆的內(nèi)存分布。
首先,Rust會在棧上分配一個大小為1byte的i8存儲a,接著會在內(nèi)存另外一個空間(不一定和a連續(xù))分配b,b中存儲的內(nèi)存空間會指向a所在的內(nèi)存空間,同時b的內(nèi)存占用大小即pointer的大小。
?
四、Array數(shù)租 和 Vector動態(tài)數(shù)組
接下來我們來看看Rust的數(shù)組Array和動態(tài)數(shù)組Vector的內(nèi)存分布,以下面的數(shù)組和動態(tài)數(shù)組為例。
let a: [i8; 3] = [1, 2, 3];
let b: Vec<i8> = vec![1, 2, 3];
數(shù)組Array是固定大小的,所以在創(chuàng)建的時候都指定好了長度;動態(tài)數(shù)組Vector,由其名字就可以知道他是可以自由伸縮的,那么我們來看看Rust是怎么在內(nèi)存上存儲這兩位數(shù)據(jù)結(jié)構(gòu)的。
對于Vector b就有點特殊啦,他會由如下三個部分組成:
-
pointer : pointer b會指向vector b在堆上的實際數(shù)據(jù)(目前是1, 2, 3 共3 * 1 byte),
-
cap(圖中上標32代表這個值和機器位數(shù)有關,最后復習一次哦): cap代表最多多少個T(本例中T是i8)的內(nèi)存可以在堆上讓這個動態(tài)數(shù)組使用,默認大小為創(chuàng)建時的T個數(shù),可根據(jù)使用需求自動擴容,但每次擴容時會帶來reallocate影響到性能。
-
len (1 machine word),代表目前有多少個T(本例中T是i8)的內(nèi)存真實被該動態(tài)數(shù)組使用。
以上即可看到數(shù)組和動態(tài)數(shù)組由于在“動態(tài)”這個特點上的不同,出現(xiàn)的內(nèi)存分布差異啦。
?
4.1 Slice 數(shù)組切片
假設我們想獲取到上面例子中a和b兩個Array和Vector的前兩個元素。
let slice_1: [i32] = a[0..2];
let slice_2: [i32] = b[0..2];
然而,對于[i32],Rust沒法在編譯時明確這個變量需要多少內(nèi)存,因而也沒法在棧上分配內(nèi)存,因而上例中的slice_1和slice_2實際上會編譯失敗。這樣的變量稱之為dynamically sized type,后續(xù)會講到string slice和trait object也屬于這個范疇。
let slice_1: &[i32] = &a[0..2]
let slice_2: &[i32] = &b[0..2]
當reference指向dynamically sized type時,Rust實際會使用到一個胖指針(fat pointer),其中包含:
-
pointer (1 machine word):指向?qū)嶋H被切片的數(shù)據(jù)。
-
length (1 machine word): 切片長度,即有多少個T(本例中T為i32)。
五、String, str, &str
let s1: String = String::from(“HELLO”);
let s2: &str = “ЗдP”; // д -> Russian Language
let s3: &str = &s1[1..3];
首先,s1是一個String,String實質(zhì)上就是Vec的一個包裝,其中也是在棧上有一個指針 + cap( 1 machine word ) + len ( 1 machine word ),指針指向了該String實際在堆上的值。String是保證UTF-8兼容的。
-
pointer (1 machine word):指向?qū)嶋H被切片的字符串。
-
length (1 machine word): 切片長度。
六、Struct
?
6.1 unit-like Struct
struct Data
?
6.2 Struct with named fields && tuple-like struct
struct Data {
nums: Vec<usize>,
dimension: (usize, usize),
}
七、Enum
enum HTTPStatus {
Ok,
NotFound,
}
對于C-style enum,在內(nèi)存中,rust會根據(jù)該enum中最大的數(shù)來選擇內(nèi)存占用最小的int來存儲,此例中沒有指定就會默認Ok為0,NotFound為1,Rust選擇占用1 byte的i8來存儲enum。
enum HttpStatus {
Ok = 200,
NotFound = 404,
}
Empty,
Number(i32),
Array(Vec<i32>),
?
?
7.1 Option
pub enum Option<T> {
None,
Some(T),
}
八、Box
(圖中省去了padding)
let t: (i32, String) = (5, “Hello”.to_string);
let mut b = Box::new(t);
文章來源:http://www.zghlxwxcb.cn/news/detail-433120.html
可以看到,原本在棧上的內(nèi)容都被轉(zhuǎn)移到Heap上,減少了我們在棧上的內(nèi)存空間消耗。文章來源地址http://www.zghlxwxcb.cn/news/detail-433120.html
到了這里,關于用了這么多年Rust終于搞明白了內(nèi)存分布!的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!