面試官問:C#的值類型和引用類型的存儲(chǔ)結(jié)構(gòu)?
.NET大牛之路 ? 王亮@精致碼農(nóng) ? 2021.08.21
我們知道,程序運(yùn)行時(shí),它的數(shù)據(jù)是存儲(chǔ)在內(nèi)存中的。當(dāng)我們的程序訪問某個(gè)變量時(shí),編譯器負(fù)責(zé)把人們可以理解的變量名轉(zhuǎn)換為處理器可以理解的內(nèi)存地址,處理器通過內(nèi)存地址找到內(nèi)存中的存儲(chǔ)單元,然后讀取其中的數(shù)據(jù)。
運(yùn)行中的 .NET 應(yīng)用程序使用兩個(gè)區(qū)域來存儲(chǔ)數(shù)據(jù):棧和托管堆,其中托管堆簡(jiǎn)稱為堆。
我們也知道,C# 中的數(shù)據(jù)類型分為兩種:值類型和引用類型。值類型包含所有的數(shù)字類型(如 byte、int、long、double 等)、布爾型(bool)、字符(char)、結(jié)構(gòu)(struct)和枚舉(enum),其它的都是引用類型(如類、接口、數(shù)組等)。
數(shù)據(jù)的類型不僅決定了數(shù)據(jù)存儲(chǔ)需要的內(nèi)存大小,還決定了對(duì)象在內(nèi)存中存儲(chǔ)的位置(?;蚨眩?。理解值類型和引用類型的特點(diǎn)和它們?cè)趦?nèi)存中的存儲(chǔ)結(jié)構(gòu),就能了解它們是如何以及何時(shí)進(jìn)行內(nèi)存分配和回收的,這有助于幫助我們編寫更高性能的應(yīng)用程序。
1棧與值類型
值類型變量的值是存儲(chǔ)在棧中的。學(xué)過數(shù)據(jù)結(jié)構(gòu)我們都知道,棧是一個(gè)后進(jìn)先出(LIFO)的數(shù)據(jù)結(jié)構(gòu)。這種數(shù)據(jù)結(jié)構(gòu)的主要特征是,數(shù)據(jù)只能從棧的頂端插入和刪除。把數(shù)據(jù)放入棧頂稱為入棧,從棧頂刪除數(shù)據(jù)稱為出棧。用圖表示如下:

棧在內(nèi)存中可以理解為上圖所示的一個(gè)個(gè)連續(xù)的存儲(chǔ)單元。棧除了存儲(chǔ)值類型的變量,還存儲(chǔ)傳遞給方法的值類型的參數(shù),以及程序當(dāng)前的執(zhí)行環(huán)境等。
我們不需要顯式地對(duì)棧做任何操作,棧中數(shù)據(jù)的生命周期由 CLR 根據(jù)其作用域直接處理的。
考慮如下代碼:
{
int a = 1; // a 的作用域開始
// ...
{
int b = 2; // b 的作用域開始
// ...
} // b 的作用域結(jié)束
} // a 的作用域結(jié)束作用域的生命周期和棧的后進(jìn)先出邏輯總是一致的。隨著代碼的執(zhí)行,程序先進(jìn)入變量 a 的作用域,再進(jìn)入 b 的作用域。對(duì)應(yīng)的,變量 a 的值先入棧,b 的值后入棧。b 的作用域先結(jié)束,它的值先出棧被銷毀,其次是 a 的值出棧被銷毀。
2堆與引用類型
托管堆是一塊內(nèi)存區(qū)域,與棧不同的是,堆中的存儲(chǔ)單元能能夠以任意順序存入和移除。
對(duì)于 .NET 程序,堆中的數(shù)據(jù)是由 CLR 托管。CLR 中的 GC(垃圾回收器)判斷程序?qū)⒉粫?huì)再訪問某數(shù)據(jù)項(xiàng)時(shí),會(huì)自動(dòng)銷毀無主的堆對(duì)象。用圖表示如下:

引用類型對(duì)象的數(shù)據(jù)存儲(chǔ)在堆中,同時(shí)也會(huì)在棧中存儲(chǔ)一個(gè)指向堆中實(shí)際數(shù)據(jù)的引用,用圖表示如下:

值得注意的是,對(duì)于引用類型的任何對(duì)象,其實(shí)例所有成員的數(shù)據(jù)都存放在堆中,無論它是值類型還是引用類型。
3小結(jié)
從數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)的特點(diǎn)來總結(jié)一下值類型與引用類型的本質(zhì)區(qū)別。
第一點(diǎn)不同是分配內(nèi)存的時(shí)機(jī)及可變性。引用類型的對(duì)象從聲明開始便分配內(nèi)存,聲明時(shí)它在內(nèi)存中占用的存儲(chǔ)單元就固定了,銷毀前不會(huì)再發(fā)生增加或減少容量,賦值只是往已分配的存儲(chǔ)單元中寫入數(shù)據(jù);引用類型是在真正賦值或初始化時(shí)才分配內(nèi)存,而且所分配的內(nèi)存大小后面可能會(huì)根據(jù)需要?jiǎng)討B(tài)發(fā)生變化(字符串類型除外)。
第二點(diǎn)不同是它們的存儲(chǔ)位置。值類型只存儲(chǔ)在棧中,只在棧頂進(jìn)行插入和刪除,遵循后進(jìn)先出原則;引用類型分兩塊存儲(chǔ),在堆中存儲(chǔ)實(shí)際的數(shù)據(jù),在棧中存儲(chǔ)指向數(shù)據(jù)的引用。
加入我們,一起踏上.NET大牛成長(zhǎng)之路↓
