Rust--如何實現內存安全的?


一、Rust的內存管理

采用虛擬內存空間在棧和堆上分配內存,這是諸多編程語言通用的內存管理基石,Rust也是一樣。然而,與c/c++語言不同的是,Rust不需要開發者顯式地通過malloc/newfree/delete之類的函數去分配和回收堆內存。

棧內存的生命周期是短暫的,會隨着棧展開(如函數調用)的過程而被自動清理。而堆內容是動態的,其分配和重新分配並不遵循某個固定的模式,所以需要使用指針來對其進行跟蹤。

Rust也引入了智能指針來管理內存。智能指針在堆上開辟內存空間,並擁有其所有權,通過存儲於棧中的指針來管理堆內存。智能指針的RAII機制利用棧的特點,在棧元素被自動清空時自動調用析構函數,來釋放智能指針所管理的堆內存間。

函數的局部變量

在函數中定義的局部變量都會被默認存儲到棧中。這和c/c++語言,甚至更多的語言行為都一樣,但不同的是,Rust編譯器可以檢查末初始化的變量,以保證內存安全。

Rust編譯器會對代碼做基本的靜態分支流程分析。

當函數調用完畢時,棧幀會被釋放,局部變量會被清空。如果變量指向堆內存,那么Rust會自動清空其指向的已分配堆內存。

Rust中的指針

Rust中的指針大致可以分為三種:引用、原生指針(裸指針)和智能指針。

  • 原生指針可以在unsafe塊下任意使用,不受Rust的安全檢查規則的限制;
  • 引用則必須受到編譯器安全檢查規則的限制;
  • 智能指針是對指針的一層封裝,提供了一些額外的功能,比如自動釋放堆內存。智能指針區別於常規結構體的特性在於,它實現了DerefDrop這兩個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的所有權機制嚴格地保證了析構函數只會調用一次,所以也不會出現非法釋放已釋放內存的情況。

三、所有權系統


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM