本篇談下Rust語言的核心概念:所有權(quán)舱污。
這個(gè)概念是支撐Rust在編譯期做內(nèi)存安全檢查的核心機(jī)制呀舔,也正是因?yàn)檫@個(gè)特性,我們認(rèn)為Rust是內(nèi)存安全的底層語言扩灯。雖然帶GC垃圾回收器的語言雖然也是內(nèi)存安全的媚赖,但由于GC的存在,已與底層無緣驴剔。
棧和堆
當(dāng)說到語言的內(nèi)存管理時(shí)省古,通常指的是對(duì)于堆的管理,而棧的使用都是自動(dòng)的丧失,通常都不需要程序員特別關(guān)心豺妓。
棧
棧,是一種數(shù)據(jù)“后進(jìn)先出”的存取方式,速度非沉帐茫快训堆。《Rust權(quán)威指南》里對(duì)于棧的形容非常貼切:
你可以把棧上的操作想象成堆放盤子:當(dāng)你需要放置盤子時(shí)白嘁,你只能將它們放置在棧的頂部坑鱼,而當(dāng)你需要取出盤子時(shí),你也只能從頂部取出絮缅。
源代碼的層面鲁沥,一般已知大小、聲明性的變量都會(huì)使用棧上的空間耕魄。當(dāng)然画恰,系統(tǒng)默認(rèn)給到程序的棧空間都不大吸奴,Windows的默認(rèn)棧只有1M允扇。
堆
堆,可以理解為在編譯期無法確定的內(nèi)存開銷则奥,需要在運(yùn)行期動(dòng)態(tài)申請(qǐng)考润。堆沒有棧高效,通常要向系統(tǒng)申請(qǐng)读处,系統(tǒng)經(jīng)過搜索糊治,找到一塊足夠大的空間,才能鎖定交付档泽。堆所能申請(qǐng)到的空間俊戳,相比棧大很多揖赴,通常是系統(tǒng)的虛擬內(nèi)存大小的級(jí)別馆匿,比如32位系統(tǒng)有4G的虛擬內(nèi)存空間,那么可以申請(qǐng)到2G~3G大小的內(nèi)存空間燥滑。
而能否直接進(jìn)行堆內(nèi)存的操作渐北,可以粗略的將編程語言分為兩類,底層和高級(jí):
能夠直接手工操作堆的語言铭拧,這類語言有著最大的靈活性赃蛛、執(zhí)行效率高,多用于系統(tǒng)編程搀菩,代表語言:C呕臂,C++。
不能直接操作堆的語言肪跋,這類語言通常帶有GC垃圾自動(dòng)回收器歧蒋,性能會(huì)受一定影響,但是開發(fā)效率高,多用于業(yè)務(wù)編程谜洽,代表語言:Java萝映,Python。
本篇要介紹的Rust的所有權(quán)機(jī)制阐虚,屬于前者序臂,具備了底層語言的靈活性;但同時(shí)卻能避免手工操作堆內(nèi)存帶來的危險(xiǎn)性实束,具備了高級(jí)語言的高效性和安全性奥秆,可謂魚和熊掌可以兼得。
接下來咸灿,為了解釋清楚Rust的堆操作機(jī)制吭练,我們先逐一看看各種堆操作方式。
堆管理方法一:純手工
80后程序員一般都經(jīng)歷過C++98的洗禮析显,那段時(shí)光鲫咽,關(guān)鍵字new和delete的魔咒就未消停過。
下面代碼是創(chuàng)建一個(gè)100x1的灰度圖像所需要的空間谷异,但是對(duì)于一個(gè)“老練的”程序員分尸,總要把下面情況爛熟于心:
總需要在圖像使用后,記得銷毀它歹嘹,否則直接造成內(nèi)存泄露箩绍;
銷毀后,原來的指針會(huì)變成“野指針”尺上,如果再次使用材蛛,或者重復(fù)釋放被重新分配的內(nèi)存,都會(huì)導(dǎo)致無法預(yù)測(cè)的錯(cuò)誤怎抛,于是我們干脆把指針變量設(shè)為NULL卑吭;
可能有段邏輯還會(huì)嘗試使用它,最好先判斷下它不為NULL马绝,避免拋異常豆赏;
上面的實(shí)踐,如果一個(gè)不小心富稻,bug就潛伏進(jìn)來掷邦,而且工程越大越難找。程序員的心智壓力可見一斑椭赋,這也是為什么我們需要《Effective C++》這本書的原因抚岗。
{
unsigned __int8* p_gray = new unsigned __int8[100]();
delete p_gray;
p_gray = NULL;
if (p_gray != NULL)
{
cout << p_gray[0] << endl;
}
}
堆管理方法二:GC垃圾回收
同樣一句創(chuàng)建8位字節(jié)數(shù)組的Java代碼,程序員只要“拿來”即可哪怔,“還回去”的事則不必親自費(fèi)心宣蔚。那誰操心呢廷痘?總得有人操心——JVM,Java虛擬機(jī)件已,更準(zhǔn)確的說笋额,是其中的自動(dòng)垃圾回收器。
自動(dòng)垃圾回收篷扩,因?yàn)椴皇浅绦騿T直接的意圖指令兄猩,所以GC就得靠自己分析垃圾特征,見機(jī)行事鉴未。自動(dòng)化內(nèi)存管理是實(shí)現(xiàn)了枢冤,但天下沒有免費(fèi)的午餐,回收時(shí)所需要的一小段時(shí)間铜秆,會(huì)讓整個(gè)Java程序進(jìn)入臭名昭著的“Stop-the-World”狀態(tài)淹真。
{
byte[] arrayRefVar = new byte[100];
}
堆管理方法三:所有權(quán)
Rust作為靜態(tài)編譯型語言,顯然沒有運(yùn)行期虛擬機(jī)的夾持连茧,那么想要做到內(nèi)存安全核蘸,就得從兩個(gè)方面下手:
自動(dòng)化內(nèi)存管理;
把內(nèi)存安全檢查提前到編譯期啸驯;
做到第一點(diǎn)并不難客扎,其實(shí)RAII(Resource Acquisition Is Initialization)已經(jīng)在C++有著很廣泛的應(yīng)用了。
RAII的思想是:資源的有效期與持有資源的對(duì)象的生命期嚴(yán)格綁定罚斗,即由對(duì)象的構(gòu)造函數(shù)完成資源的分配徙鱼,同時(shí)由析構(gòu)函數(shù)完成資源的釋放。在這種要求下针姿,只要對(duì)象能正確的析構(gòu)袱吆,就不會(huì)出現(xiàn)資源泄露問題。
C++應(yīng)用RAII是以模式(Pattern)距淫,或者最佳實(shí)踐這種松散方式來實(shí)現(xiàn)的绞绒。Rust要想做到第二點(diǎn),就需要把這種思想集成進(jìn)語言本身溉愁,讓編譯器能看得懂处铛。
Rust提出了所有權(quán):
Rust中的每個(gè)值都有一個(gè)對(duì)應(yīng)的變量作為它的所有者饲趋;
在同一時(shí)間內(nèi)拐揭,只有且僅有一個(gè)所有者;
當(dāng)所有者離開自己的作用域時(shí)奕塑,它持有的值就會(huì)被釋放掉堂污。
我們用Rust再實(shí)現(xiàn)一次創(chuàng)建8位無符號(hào)整數(shù)數(shù)組:
{
let v: Vec<u8> = vec![0;100];
} // v作為數(shù)組的所有者,在離開作用域時(shí)龄砰,銷毀了所持有的內(nèi)存盟猖。</pre>
和Java一樣讨衣,只需要一行代碼就完成了在堆上的內(nèi)存申請(qǐng)。但Rust做得更多——在離開作用域的同時(shí)式镐,確定性的銷毀了堆上的內(nèi)存反镇,而完全不需要一個(gè)拖泥帶水的GC。
可謂干凈利落娘汞,身手不凡歹茶。