一、Rust的記憶體管理
采用虛拟記憶體空間在棧和堆上配置設定記憶體,這是諸多程式設計語言通用的記憶體管理基石,Rust也是一樣。然而,與c/c++語言不同的是,Rust不需要開發者顯式地通過malloc/new或free/delete之類的函數去配置設定和回收堆記憶體。
棧記憶體的生命周期是短暫的,會随着棧展開(如函數調用)的過程而被自動清理。而堆内容是動态的,其配置設定和重新配置設定并不遵循某個固定的模式,是以需要使用指針來對其進行跟蹤。
Rust也引入了智能指針來管理記憶體。智能指針在堆上開辟記憶體空間,并擁有其所有權,通過存儲于棧中的指針來管理堆記憶體。智能指針的RAII機制利用棧的特點,在棧元素被自動清空時自動調用析構函數,來釋放智能指針所管理的堆記憶體間。
函數的局部變量
在函數中定義的局部變量都會被預設存儲到棧中。這和c/c++語言,甚至更多的語言行為都一樣,但不同的是,Rust編譯器可以檢查末初始化的變量,以保證記憶體安全。
Rust編譯器會對代碼做基本的靜态分支流程分析。
當函數調用完畢時,棧幀會被釋放,局部變量會被清空。如果變量指向堆記憶體,那麼Rust會自動清空其指向的已配置設定堆記憶體。
Rust中的指針
Rust中的指針大緻可以分為三種:引用、原生指針(裸指針)和智能指針。
原生指針可以在unsafe塊下任意使用,不受Rust的安全檢查規則的限制;
引用則必須受到編譯器安全檢查規則的限制;
智能指針是對指針的一層封裝,提供了一些額外的功能,比如自動釋放堆記憶體。智能指針差別于正常結構體的特性在于,它實作了Deref和Drop這兩個trait。 Deref提供了解引用能力,Drop提供了自動析構的能力,正是這兩個trait讓智能指針擁有了類似指針的行為。比如String和Vec類型就是一種智能指針。
RAII(構造和析構)
RAII使用構造函數來初始化資源,使用析構函數來回收資源。這是指在定義對象的時候實作一個析構函數負責釋放資源,在變量作用域結束的時候,編譯器會自動幫我們加上對析構函數的調用,我們使用這樣的對象時,就不需要手動釋放資源,進而實作了資源的自動釋放。
RAII與GC最大的不同在于,RAII将資源托管給建立堆記憶體的指針對象本身來管理,并保證資源在其生命周期内始終有效,一旦生命周期終止,資源馬上會被回收。
二、Rust的記憶體安全
記憶體不安全的例子
空指針
解引用空指針是不安全的。這塊位址空間一般是受保護的,對空指針解引用在大部分平台上會産生segfaul。
野指針
野指針指的是未初始化的指針。它的值取決于這個位置以前遺留下來的是什麼值。是以它可能指向任意一個地方。對它解引用,可能會造成degfault,也可能不會,純粹看運氣。但無論如何,這個行為都不會是你預期内的行為,是一定定會産生bug的。
懸空指針
懸空指針指的是記憶體空間在被釋放了之後,繼續使用。它跟野指針類似,同樣會讀寫已經不屬于這個指針的内容。
使用末初始化記憶體
不隻是指針類型,任何一種類型不初始化就直接使用都是危險的,造成的後果我們無法預測。
非法釋放記憶體
配置設定和釋放要配對。如果對同一個指針釋放兩次,會制造出記憶體錯誤。如果指針并不是記憶體配置設定器傳回的值,對其執行釋放操作,也是危險的。
緩沖區溢出
指針通路越界了,結果也是類似于野指針,會讀取或者修改臨近記憶體空間的值,造成危險。
Rust是如何解決記憶體安全問題的?
使用末定義記憶體
Rust中的變量必須初始化以後才可使用,否則無法通過編譯器檢查。
開發者沒有任何辦法去建立一個空指針。Rust中使用Option類型來代替空指針,Option實際是枚舉體,包含兩個值:Some(T) 和 None,分别代表兩種情況,有和無。這就迫使開發者必須對這兩種情況都做處理,以保證記憶體安全。
懸空指針指的是記憶體空間在被釋放了之後,繼續使用。Rust通過所有權和借用機制解決這個問題。
Rust編譯器在編譯期就能檢查出資料越界的問題,進而完美地避免了緩沖區溢出。
非法釋放末配置設定的指針或已經釋放過的指針
Rust中不會出現未配置設定的指針,是以也不存在非法釋放的情況。同時,Rust的所有權機制嚴格地保證了析構函數隻會調用一次,是以也不會出現非法釋放已釋放記憶體的情況。
三、所有權系統