你真的了解C 中的值和引用嗎?(上)

2021-12-29 20:14:48 字數 1303 閱讀 1266

術語解釋

注意,上面我說的都是值型別表示式和引用型別表示式,包括區域性變數和成員(如欄位、屬性、索引器)等。現在,我們來考慮以下問題:

對於上面這些問題,您的答案是什麼呢?

誤區:值型別到底儲存在哪?

在談到值型別和引用型別的區別時,很多初學者常說值型別分配在方法的呼叫棧(或執行緒棧)上,引用型別分配在託管堆上,這種說法是錯誤的,至少前半部分是錯誤的。實際上這根本不應該成為值型別和引用型別區別的答案,這是所答非所問。值型別和引用型別的區別在語義層面,與儲存位置無關,並不是值型別和引用型別不同的分配方式導致了它們行為上的差異,而是因為值和*引用*這兩種型別在語義上的差異,才導致了他們不同的分配方式。本文只討論儲存位置,不會深入介紹它們的區別。

有些朋友可能會說,詳細的分配方式應該是這樣的:

具體來講也就是說,當值型別作為引用型別的私有欄位時,它將作為引用型別例項的一部分,也分配在託管堆上。而當引用型別作為值型別的成員變數時,棧上將保留該成員的引用,其實際資料還是儲存在堆中。

在c# 2出現之前,這樣的說法沒有問題。但c# 2引入了匿名方法和迭代器塊後,以上說法就過於籠統了,它只看到了**層面的東西,而沒有看到編譯器層面的東西。值型別例項作為區域性變數不都是分配在棧上。這是因為c#**中的區域性變數,很可能在編譯為il後就不再是區域性變數了。比如,如果匿名方法使用了外部變數(外部方法中宣告的區域性變數),或者迭代器塊中宣告了變數,那麼這些變數將被提公升為隱藏類的字段,因此也將分配在堆上

由此可見,雖然msdn的文件上也說,「值型別分配在棧上」,但這顯然是不合適的。因為

這樣,關於值型別的儲存位置,正確、完整的說法應該是:對於值型別來說,在微軟桌面clr的c#實現中,如果值型別的例項是區域性變數、lambda表示式或匿名方法中封閉的臨時變數,且方法體不是迭代器塊,並且jit不對該值進行寄存,那麼這時該值型別將儲存在棧上。

夠囉嗦吧,其實每一句都必不可少:

之所以會有這樣的誤區,是因為人們總是錯誤地以為型別系統與儲存分配策略有關。然而究竟是儲存在棧上還是堆上,與要儲存的型別沒有任何關係。分配機制的選擇只與儲存所需的生存時間(lifetime)有關。

明確了這些之後,我們可以得出以下結論:

現在我們來看一下實現細節。在微軟clr對c#的實現中:

這樣就可以很自然地得出:

一旦你摒棄值的型別與儲存有關這個瘋狂的想法,一切就會豁然開朗了。其實,你無需知道這些,除非要編寫不安全**或與非託管**互動。你盡可以讓編譯器和執行時來管理儲存位置的生存時間,這正是它們所擅長的。

誤區:引用就是位址

你真的了解C 中的值和引用嗎?(下)

今天要討論的話題是引數傳遞,這不是實現細節。c 中的引數共分為4種 本文主要討論引數按值傳遞和按引用傳遞的區別,以及值型別和引用型別在按值傳遞和按引用傳遞時的表現。我們均以向方法傳遞引數為例。c 的引數在預設情況下都是按值傳遞的。也就是說,當向方法傳遞引數的時候,會建立乙個新的儲存位置,然後將引數的...

C 的那些事 你真的了解引用嗎

說到引用,一般c 的教材中都是這麼定義的 1,引用就是乙個物件的別名。2,引用不是值不佔記憶體空間。3,引用必須在定義時賦值,將變數與引用繫結。那你有沒有想過,上面的定義正確嗎?編譯器是如何解釋引用的?這裡先給出引用的本質定義,後面我們再進一步論證。1,引用實際是通過指標實現的。2,引用是乙個常量指...

你真的了解EOF和feof嗎?

判斷檔案結束有兩種方法 eof和feof 檢視stdio.h可以看到如下定義 define eof 1 define ioeof 0x0010 define feof stream stream flag ioeof 由此可以看出,這兩種方式的原理是不同的。有人說eof只能用於文字檔案,其實不然,還...