最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

Rust語(yǔ)言學(xué)習(xí)之內(nèi)存模型分析

2023-02-18 02:59 作者:zerix  | 我要投稿

## 背景

隨著Rust越來(lái)越多的應(yīng)用良好表現(xiàn),吸引了越來(lái)越多的開(kāi)發(fā)者關(guān)注和各領(lǐng)域?qū)?yīng)的Rust解決方案出現(xiàn)。對(duì)于從C/GO/Java/Python這些語(yǔ)言開(kāi)發(fā)者來(lái)說(shuō),學(xué)習(xí)Rust語(yǔ)言最大的挑戰(zhàn)就是需要理解Rust語(yǔ)言的內(nèi)存管理模型。而Rust開(kāi)創(chuàng)性的所有權(quán)管理機(jī)制,是我們理解和精通該門(mén)語(yǔ)言必須要首先弄清楚的要點(diǎn)。而這也是為什么大家一致認(rèn)為Rust的學(xué)習(xí)曲線陡峭的最核心原因。

>本系列文章主要是閱讀圖靈系列叢書(shū)《Rust程序設(shè)計(jì)》讀書(shū)筆記,再以有經(jīng)驗(yàn)程序員新學(xué)Rust語(yǔ)言的路線來(lái)編寫(xiě)。


## 示例

我們首先來(lái)看一段代碼:

這段代碼邏輯很簡(jiǎn)單,僅僅是定義了三個(gè)變量,并做一些賦值和打印操作。三個(gè)變量定義如下:

1.變量v,定義v為一個(gè)向量,其內(nèi)包含了6個(gè)Int類(lèi)型數(shù)字。

2.變量r,再將v的地址賦值給變量r,相當(dāng)于r是v向量變量的引用。

3.變量aside,最后將變量v賦值給變量aside。

最后打印r變量的第0個(gè)元素。

如果根據(jù)我們以往的編程經(jīng)驗(yàn),該段代碼找不出來(lái)任何邏輯和寫(xiě)法問(wèn)題,但是在Rust中,編譯運(yùn)行該段代碼,其會(huì)報(bào)錯(cuò):

從這段錯(cuò)誤,我們可以得出以下結(jié)論:

1.Rust能夠在編譯階段發(fā)現(xiàn)代碼runtime階段內(nèi)存錯(cuò)誤問(wèn)題。

2.Rust語(yǔ)言編譯時(shí)能夠非常詳盡的解釋編譯錯(cuò)誤。

3.編譯器提示在將v的引用賦值給r時(shí),相當(dāng)于將v借用給了r,借用完成后,再次將v賦值給aside操作時(shí),所有權(quán)出現(xiàn)了Move操作,v的所有權(quán)到了變量aside之上,這個(gè)時(shí)候,再去訪問(wèn)r變量,就會(huì)出現(xiàn)報(bào)錯(cuò),相當(dāng)于訪問(wèn)了一個(gè)懸空指針。

而這個(gè)報(bào)錯(cuò),正是由于Rust的所有權(quán)機(jī)制導(dǎo)致的。那么,我們這篇文章主要是為了解釋下面兩個(gè)問(wèn)題:

1.Rust所有權(quán)制度到底是為了解決什么問(wèn)題出現(xiàn)的?

2.Rust所有權(quán)制度如何解決該問(wèn)題的?


## Rust內(nèi)存模型


在我們過(guò)往的編程語(yǔ)言使用經(jīng)驗(yàn)中,我們都了解類(lèi)似C/C++語(yǔ)言,內(nèi)存管理都是靠程序員手動(dòng)來(lái)維護(hù)的,new/free的操作都是程序員自己去控制,其性能非常高,但是由此引入了非常多的內(nèi)存問(wèn)題,比如訪問(wèn)了已經(jīng)釋放了的內(nèi)存地址,或者內(nèi)存沒(méi)有是否導(dǎo)致內(nèi)存泄漏從而系統(tǒng)不穩(wěn)定等等問(wèn)題。為了最大限度避免這些問(wèn)題,在使用C/C++語(yǔ)言時(shí),大多數(shù)程序員都要手寫(xiě)一個(gè)內(nèi)存池來(lái)進(jìn)行內(nèi)存管理,而由此帶來(lái)的內(nèi)存碎片等問(wèn)題都不容易處理。而Java類(lèi)的,內(nèi)存管理都由Jvm虛擬機(jī)來(lái)完成,減少了程序員的出錯(cuò),但是Jvm虛擬機(jī)進(jìn)行自動(dòng)內(nèi)存整理(GC)時(shí),又帶來(lái)了很大的CPU波動(dòng)。影響了業(yè)務(wù)系統(tǒng),導(dǎo)致業(yè)務(wù)系統(tǒng)在高性能計(jì)算時(shí)由于GC出現(xiàn)而出現(xiàn)大幅度時(shí)延。

而Rust即想得到C/C++手動(dòng)管理內(nèi)存的性能,又想自動(dòng)化去管理內(nèi)存,還要避免類(lèi)似Java統(tǒng)一GC的性能損失。不得不說(shuō),這種成年人的選擇總會(huì)讓人為之精神一振。在此背景下,Rust所有權(quán)系統(tǒng)應(yīng)運(yùn)而生,做到了熊掌與魚(yú)兼得,后續(xù),我們主要分析下Rust所有權(quán)系統(tǒng)是如何工作的。


## Rust內(nèi)存結(jié)構(gòu)示例


Rust所有權(quán)系統(tǒng)主要是用來(lái)做自動(dòng)化內(nèi)存管理,那么,我們首先分析下Rust代碼內(nèi)存結(jié)構(gòu)。其示意圖如下所示:

和C語(yǔ)言類(lèi)似,Rust程序內(nèi)存布局包括了堆、棧、靜態(tài)數(shù)據(jù)區(qū)、只讀數(shù)據(jù)區(qū)和只讀代碼區(qū)。

其中,對(duì)于每個(gè)區(qū)存放的內(nèi)容,大抵可以如下分類(lèi):

1.棧:在編譯階段就可以確定哪些數(shù)據(jù)可以存放到棧上,由編譯器管理,函數(shù)局部變量等,存放到棧中。

2.堆:由程序員編寫(xiě)的代碼來(lái)申請(qǐng)使用,一般做大量數(shù)據(jù)讀寫(xiě)時(shí)使用,運(yùn)行時(shí)申請(qǐng)。

3.靜態(tài)數(shù)據(jù)區(qū):一般的靜態(tài)函數(shù)、靜態(tài)局部變量和靜態(tài)全局變量存放區(qū)域,在程序啟動(dòng)時(shí)初始化。

4.Literals(只讀數(shù)據(jù)區(qū)):存放代碼的文字常量區(qū)域。

5.Instructions(只讀代碼區(qū)):存放可執(zhí)行代碼區(qū)域。


我們以一段Rust代碼來(lái)距離,了解下Rust中各變量是如何存儲(chǔ)的,代碼如下:

該段代碼內(nèi)存布局如下圖所示:

分析內(nèi)存結(jié)構(gòu)圖,我們可以了解到下面結(jié)論:

1.noodles,oodles,poodles三個(gè)變量都存儲(chǔ)在棧上,并且都是三個(gè)胖指針。

2.noodles和oodles是指向同一塊內(nèi)存,只不過(guò)指針首地址不一樣。

3.poodles變量的數(shù)據(jù)內(nèi)容是存儲(chǔ)在預(yù)分配的只讀內(nèi)存區(qū)。

4.noodles的變量存儲(chǔ)格式包括三個(gè)部分,第一個(gè)部分是指向數(shù)據(jù)存儲(chǔ)堆內(nèi)存首地址,第二個(gè)部分是該變量的容量,第三個(gè)部分是該變量的長(zhǎng)度。(實(shí)際上Rust里String是用Vec來(lái)實(shí)現(xiàn)的,所以這里的容量是Vec管理策略來(lái)決定,Rust里分配原則是2->4->8,如果容量不夠,下次申請(qǐng)的為前一次的2倍。)

5.字符串常量poodles的內(nèi)存是提前分配好的只讀內(nèi)存區(qū)。

6.引用并不做深度拷貝操作,僅僅是指針指向數(shù)據(jù)堆內(nèi)存地址。


## 多語(yǔ)言?xún)?nèi)存賦值解析


內(nèi)存管理體現(xiàn)在每個(gè)語(yǔ)言對(duì)賦值操作的實(shí)現(xiàn)中,我們可以對(duì)比下Python、C++和Rust這幾種比較有代表性的語(yǔ)言,了解下他們各自對(duì)賦值操作內(nèi)存是如何管理的。


### Python


我們以下面代碼為例:

整個(gè)操作的內(nèi)存變化如下圖所示:

我們分析可以得出結(jié)論:

1.Python的字符串和列表底層都是胖指針的形式存儲(chǔ),列表指針的存儲(chǔ)內(nèi)容為:引用計(jì)數(shù),列表長(zhǎng)度,列表數(shù)據(jù)指針,列表容量。字符串指針的存儲(chǔ)內(nèi)容為:引用計(jì)數(shù),字符串長(zhǎng)度,文本數(shù)據(jù)內(nèi)容。

2.局部變量存儲(chǔ)在棧中。

3.賦值操作過(guò)程為,t = s,新建一個(gè)對(duì)象t,指向s的內(nèi)存地址,并將s對(duì)象的引用計(jì)數(shù)+1。u = s,再新建一個(gè)對(duì)象u,指向s的內(nèi)存地址,并將s對(duì)象的引用計(jì)數(shù)+1,s對(duì)象的引用計(jì)數(shù)為3,表示被3個(gè)對(duì)象使用。

4.釋放s內(nèi)存數(shù)據(jù)得維護(hù)s的引用計(jì)數(shù),引用計(jì)數(shù)為0時(shí)可以清理該內(nèi)存數(shù)據(jù)。


### C++


對(duì)應(yīng)的,C++賦值示例代碼如下:

該段代碼內(nèi)存結(jié)構(gòu)變化如下圖所示:

分析后,我們可以得出結(jié)論:

1.向量s局部變量在內(nèi)存中存儲(chǔ)在棧中。其也是一個(gè)胖指針,三個(gè)字段內(nèi)容為向量數(shù)據(jù)堆內(nèi)存地址,向量占用空間大小,向量長(zhǎng)度。堆內(nèi)存地址數(shù)據(jù)存儲(chǔ)也是三個(gè)胖指針,指針地址字段指向的分別是三個(gè)字符串的內(nèi)存地址。

2.t = s操作過(guò)程實(shí)際上是復(fù)制了一份s對(duì)象的數(shù)據(jù),包括堆內(nèi)存數(shù)據(jù),并將新的堆內(nèi)存數(shù)據(jù)指向t胖指針的堆內(nèi)存數(shù)據(jù)地址。

3.u = s操作過(guò)程和t = s操作過(guò)程一致。

4.完成賦值操作后,內(nèi)存中有三份s對(duì)象一樣的數(shù)據(jù),存儲(chǔ)在不同的堆中。

5.釋放s,t,u三個(gè)對(duì)象內(nèi)存很簡(jiǎn)單,各自維護(hù)自己生命周期即可。


### Rust


最后,我們?cè)賮?lái)看Rust賦值代碼:


Rust內(nèi)存結(jié)構(gòu)變化如下圖所示:

我們可以得出結(jié)論:

1.Rust中向量存儲(chǔ)和字符串存儲(chǔ)方式和C++一樣,都是胖指針,指針內(nèi)容格式一致。

2.t = s操作,將t胖指針的堆內(nèi)存數(shù)據(jù)地址指向s的堆內(nèi)存數(shù)據(jù)地址,s對(duì)象變成懸空指針,無(wú)法訪問(wèn)。

3.u = s操作,會(huì)報(bào)錯(cuò),此時(shí)s為懸空指針,不能訪問(wèn)。

4.向量s在堆內(nèi)存中數(shù)據(jù)只有一份。

5.釋放數(shù)據(jù)的操作也簡(jiǎn)單,因?yàn)槎褍?nèi)存中只有一份數(shù)據(jù),在脫離了作用域后會(huì)自動(dòng)釋放內(nèi)存數(shù)據(jù)。


### 三類(lèi)語(yǔ)言對(duì)比


對(duì)比C++,Python和Rust語(yǔ)言在相同賦值語(yǔ)句下,其內(nèi)存布局變化,我們可以很直觀的得出下面結(jié)論:

1.整個(gè)賦值過(guò)程占用內(nèi)存最小的是Rust語(yǔ)言和Python語(yǔ)言。

2.C++語(yǔ)言賦值操作最為笨重,需要做數(shù)據(jù)深度拷貝。

3.在釋放內(nèi)存操作時(shí),最高效簡(jiǎn)單的是Rust語(yǔ)言和C++語(yǔ)言。

4.Python語(yǔ)言在釋放內(nèi)存時(shí)需要維護(hù)引用計(jì)數(shù),較為復(fù)雜。


>注意,這里只是對(duì)比最常用情況,實(shí)際上Rust也支持類(lèi)似Python的引用計(jì)數(shù)內(nèi)存管理方法和C++的深度拷貝操作。有興趣可以去了解相關(guān)文檔。


## 引用介紹


在上述變量賦值操作過(guò)程中,實(shí)際上是一個(gè)變量所有權(quán)轉(zhuǎn)移的過(guò)程。那么,是否可以直接使用類(lèi)似C語(yǔ)言指針的方式去操作一個(gè)變量呢?答案是肯定的,Rust提供一種引用的數(shù)據(jù)類(lèi)型來(lái)完成此目的。

>Rust中對(duì)變量的引用,稱(chēng)之為借用(Borrowing),使用完畢后,需要?dú)w還。


我們來(lái)看一段示例代碼:

這段代碼中,是用了Rust的Rc包來(lái)創(chuàng)建一個(gè)可以被多個(gè)變量同時(shí)借用的引用變量,Rust中還提供一個(gè)Arc包來(lái)實(shí)現(xiàn)相同功能,區(qū)別是Rc非線程安全型,Arc是線程安全型的。Rc使用引用計(jì)數(shù)方式來(lái)實(shí)現(xiàn),Arc是Atomic Rc。Arc相比Rc會(huì)額外帶來(lái)性能損耗,需要用戶根據(jù)場(chǎng)景選用。


引用適用領(lǐng)域:

1.圖操作中一個(gè)點(diǎn)被多個(gè)邊包含。

2.一個(gè)變量被多個(gè)線程同時(shí)操作。


上段代碼在內(nèi)存中的存儲(chǔ)格式為:

為了保證整個(gè)機(jī)制在各個(gè)場(chǎng)景下的可靠,不出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng)情況,Rust引入了所有權(quán)樹(shù)。


## 所有權(quán)樹(shù)


Rust中,所有權(quán)規(guī)則總結(jié)如下:

1.Rust中的每個(gè)值都有一個(gè)被稱(chēng)為其所有者的變量(即:值的所有者是某個(gè)變量)。

2.值在任一時(shí)刻有且只有一個(gè)所有者。

3.當(dāng)所有者(變量)離開(kāi)作用域,這個(gè)值將被銷(xiāo)毀。


如下圖所示,變量的所有權(quán)可以被借用,主要包括了可變引用和非可變引用。這里的可變引用和不可變引用可以使用讀寫(xiě)鎖來(lái)理解。通常來(lái)說(shuō),只讀的不可變引用即是只讀引用,變量可以被多個(gè)只讀引用來(lái)同時(shí)借用,由于是只讀的,所以不存在變量共享問(wèn)題。而可修改引用,則要求一個(gè)變量只能被一個(gè)可修改引用借用,而且,對(duì)該變量的訪問(wèn),只能通過(guò)該可變引用來(lái)訪問(wèn)。規(guī)則如下圖所示:

所有權(quán)樹(shù)表示了所有權(quán)行為都是可以推導(dǎo)的,也就是說(shuō)在編譯階段編譯器即可發(fā)現(xiàn)各類(lèi)的內(nèi)存管理問(wèn)題,所以這也是Rust內(nèi)存安全性的保證。



## 所有權(quán)示例代碼分析


>大家可以通過(guò)下面的代碼片段及后面注釋中的解釋來(lái)理解Rust的所有權(quán)樹(shù)。


1.代碼片段1:

2. 代碼片段2:

3. 代碼片段3:

## 思考


1.Rust中是否不存在內(nèi)存泄漏?


如下圖所示,在Rust中可以創(chuàng)建引用循環(huán),在此情況下,引用計(jì)數(shù)永遠(yuǎn)不可能為0,就會(huì)發(fā)生內(nèi)存泄漏。

為了避免該問(wèn)題,Rust中引入了RefCell機(jī)制,該機(jī)制不在本文詳細(xì)描述,大家可以搜索下相關(guān)文章,后續(xù)其他文章也會(huì)專(zhuān)門(mén)講解。


Rust語(yǔ)言學(xué)習(xí)之內(nèi)存模型分析的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
蓬莱市| 高碑店市| 驻马店市| 靖边县| 桂平市| 兰西县| 日土县| 江都市| 巫溪县| 长阳| 朝阳市| 清苑县| 通江县| 海伦市| 嘉祥县| 庐江县| 樟树市| 浙江省| 资兴市| 湖口县| 峨山| 江陵县| 灌南县| 宝鸡市| 广宁县| 鄂托克前旗| 刚察县| 贵定县| 泰来县| 留坝县| 建昌县| 花莲县| 濉溪县| 禄丰县| 林西县| 宽城| 洞头县| 广安市| 乌苏市| 浮山县| 延安市|