一直以爲對於引用類型做爲參數在方法調用時加不加 ref 關鍵字是沒有區別的。但是今天一調試蹤了一下變量內存情況才發現大有不同。
直接上代碼,結論是:以下代碼是使用了 ref 關鍵字的版本,它輸出10;如果不使用ref 關鍵字則輸出 1,2,3
一些說明:
- 以上代碼中的註釋可縱向分隔爲兩部分來看,左邊部分是不加ref關鍵字調試時查看的內存情況,右邊則是加上ref關鍵字後的情況。
- 每個/* */中註釋都是代碼執行完註釋所在位置的上一語句後的內存情況。
- &myArray //表示獲取這個變量內存的指令
0x000000c088d7e3d0 //表示這個變量在內存中的地址
*&myArray: 0x0000029b3f84ae00 //表示這個變量指向的內存空間的對象的地址
- 在visual studio 2019 中查看變量內存地址的方法:
方法一:
在即時窗口輸入取地址符+變量名如 &a 這是會輸出如下 兩行:
0x000000325637e570
*&a: 0x00000209ba0dad58
第一行 0x000000325637e570 代表變量本身的內存地址,第二行 *&a: 0x00000209ba0dad58 表示變量指向的對象的內存地址
方法二:
【調試】-【窗口】-【內存】-從列出來的4箇中選一個,然後會調出內存查看窗口。在內存查看地窗口中的【地址】裏輸入[取地址符]+[變量名]如 &a ,這時地址中的&a會變成變量的十進制表示的內存地址,如:0x000000325637E570
補充幾張調試中斷在不同語句時的一些內存情況截圖:(加上ref關鍵字後的引用類型傳參情況圖)
1.
2.
3.
4.
2022-07-31再次總結:
我們知道,不論值類型還是引用類型,內存存儲單元中的數據是依靠存儲單元地址來訪問的。
對於值類型數據的內存模型就是直接把值放在內存單元裏,需要訪問值時直接用內存地址就能獲取這個地址中存儲的數據了。這個模型直觀而簡單很好理解。
而引用類型的內存存儲模型是由棧內存+堆內存的結構共同實現的。具體細節就是:引用類型變量的數據內容(命名爲content)放在堆內存(我們給這個堆內存地址一個名字叫H),然後還需要有一個棧內存(再把棧內存地址命名爲S),這個地址爲S的棧內存裏存放的值就是H,是的 就是堆內存的地址,這樣就要訪問content就需要先訪問S,得到S中的內容纔得到了地址H,最後才能訪問到H地址裏的內容content。基於S中存放的值是另一個內存地址而不是數據內容本身的原因,所以人們常把S及其值叫做指針(引用類型數據使用的正是這種間接訪問數據的設計模型)。
接下來是在C#語言的方法中,對傳遞引用類型參數的設計及實現細節的說明。
先不考慮ref關鍵字,對於方法的引用類型參數,其在方法接收外部變量時的接收細節是這樣:方法內部會創建一個新的棧內存也就是個指針,其內存單元就是用來接收那個外部傳進來的變量所在的堆內存的地址,採用這樣的方式來實現對外部變量的接收也就是說本質上是傳遞堆內存地址。但是注意,外部變量原來那個棧內存指針也指向同樣的堆內存。即在方法內部和外部這兩個指針都指向同一塊堆內存但這兩個指針各是各,是不同的棧內存地址。基於這種設計,我們可以看出,在前面的示例中,如果不使用ref關鍵字,則在SetArray方法內部 array=new int[]...這行指令實際上是先按new int[]指令創建了一個新的堆內存(放新的數組),然後把指針array的存儲的值(賦值前它是原堆內存地址)更新爲新的堆內存的地址,那麼原堆內存地址在方法內部也就無法再訪問了,而且這個地址值更新的進程也與方法外部的指針myArray無關,即方法外部的myArray依然指向它原先那個堆內存地址。
最後,我們來考慮ref關鍵字。一個方法的引用類型參數使用 ref 關鍵字後會使得在方法在接收外部變量時改變默認傳遞參數的行爲。具體表現就是加上ref後,在方法內部不再在棧內存上創建一個新的指針用來接收外部變量其堆內存的地址,而是直接使用外部變量的指針,等於把外部變量的指針本身給傳進來了。
關鍵歸納:不加ref傳外部變量堆內存地址;加上ref傳外部變量的棧內存地址即指針地址本身。
我覺得這次總結的還不錯,希望對您有所幫助。也希望自己不要再忘記這些關鍵的知識點了。