原來是這樣 C 中new乙個物件時,發生了什麼事?

2022-03-31 03:42:06 字數 2223 閱讀 8036

問:當我們new乙個物件時,會發生什麼事?

答:呼叫該型別的建構函式。

問題看似簡單,不過事實上,clr做的比這要多。。。

要準確回答這個問題,還要分情況來說。

new乙個引用型別

首先,要例項化乙個引用型別,就一定需要在堆上分配記憶體。要分配記憶體,就需要先計算出這個引用型別占多大空間,需要給它分配多少記憶體。

那怎麼計算呢?簡單!只要計算該型別所有欄位的長度總和就可以啦。我們知道,引用型別的字段,佔乙個指標的長度(32位機器上是4個位元組,64位機器上是8個位元組)。值型別的字段長度可以通過遞迴的方法計算得出(遞迴終點是遇到引用型別或基本型別)。根據這些資訊,我們就可以輕鬆計算出所有字段長度的總和了。

但是實際上計算方法會比這個複雜一點點,因為還要考慮到記憶體對齊的情況,關於記憶體對齊的解釋我附在了本文的最後,這裡就不多說了。考慮了記憶體對齊之後,得到的結果可能會比之前的要稍大一些。

不過這個仍然不是最終的結果。要得到最終的結果,還需要加上兩個指標的長度。原因是,每個分配在堆上的物件都會有兩個指標的「額外開銷」,這兩個開銷分別是同步塊索引和型別指標。關於同步塊索引,一兩句話也說不清楚,不過可以把它簡單地理解成乙個指向「同步塊」的指標,而這個「同步塊」的作用則是為了讓擁有該同步塊的物件能夠支援執行緒同步。所謂型別指標,你可以這樣來理解:每個物件都是乙個型別的例項,而每個型別本身都有乙個type型別的例項來表示,物件的型別指標就是指向該型別的type例項的指標。舉個例子就清楚多了,我們知道,typeof(string)的值是乙個type型別的例項,這個type型別的例項也就是所有的string物件的型別指標所指向的東西。

好了,到此為止,就可以得出例項化乙個引用型別需要為其分配的記憶體數了。不過,要注意的是,clr並不是在執行時計算分配記憶體的大小的,而是早在編譯的時候就已經計算好這個量了。

接下來要做的是初始化分配得到的記憶體塊。這個很簡單,只要把這段記憶體的所有二進位制位都設為0就可以了。

然後就是初始化兩個「額外開銷」的值了。對於同步塊索引,clr把它初始化為乙個負數,並不指向任何的同步塊。這是因為對於絕大多數物件,我們不要求它支援執行緒同步,所以不必急著給他例項化乙個同步塊,等到真的需要的時候再實際進行分配。而對於型別指標,則將其指向乙個實實在在的物件——即該型別的型別物件例項。

再然後,就是呼叫型別的建構函式了。

完成了上述步驟,乙個引用型別的物件例項就做好了,new操作符只要返回這個例項的引用就算完成任務了。

new乙個值型別

首先,也是要計算需要分配多少記憶體。因為值型別是沒有所謂的「額外開銷」的,所以值型別所需的記憶體長度就是其內部欄位的大小總和(同樣需要考慮記憶體對齊)。同樣的,clr在編譯的時候就已經計算好這個量了,不需要在執行時計算。

然後,clr分配所需的記憶體。在**分配呢?這可說不准,在堆上或在棧上都有可能。

再然後就是呼叫型別建構函式了。這裡需要注意,clr並沒有初始化這段記憶體塊,而是把初始化記憶體塊的任務都交給建構函式了。這樣做是為了保證值型別輕量性的特點。這也是為什麼c#語言在值型別的建構函式中強制要求為所以字段賦值的原因。另外,所有值型別的預設建構函式都會把內部欄位都初始化為0。

到此,乙個值型別也做好了。一般來說,對於值型別,new操作符並不需要返回其位址。原因在於,值型別的位置相對固定,因此在編譯時就可以基本確定它們的位置。比如說,函式棧上的值型別例項都有乙個相對於棧的偏移量,這個偏移量在編譯時就是確定的。再比如說,作為引用型別的字段的值型別,都有乙個相對於該引用型別位址的偏移量,這個偏移量也是早在編譯時就固定下來的。所以,new操作符無需返回值型別例項的位址。

現在我們知道每new乙個物件時clr所需要做的工作了。可以看出,clr的任務並不輕鬆。若是考慮到new乙個物件之後還要垃圾**該物件,那clr就更辛苦了。所以,每當我們想要例項化乙個型別的時候,都需要三思而後行。。。

附:關於記憶體對齊(這個是我之前學習的筆記,記得不是很系統,有興趣的同學湊合看一下吧。。。)

為什麼要記憶體對齊?

為了提高程式的效能,記憶體中的資料結構應該盡可能地在自然邊界上對齊。原因在於,為了訪問未對齊的記憶體,處理器需要作兩次記憶體訪問,而對齊的記憶體訪問僅需要一次訪問。(對字,雙字,和四字來說,自然邊界分別是偶數字址,可以被4整除的位址,和可以被8整除的位址。)

怎樣才算記憶體對其?

乙個字或雙字運算元跨越了4位元組邊界,或者乙個四字運算元跨越了8位元組邊界,被認為是未對齊的,從而需要兩次匯流排週期來訪問記憶體。乙個字起始位址是奇數但卻沒有跨越字邊界被認為是對齊的,能夠在乙個匯流排週期中被訪問。

原來是Struts2 0的乙個Bug

開始使用的是struts2.06版本。在配置struts.xml的時候,發現了乙個問題 struts.xml xml struts include file struts default.xml package name default extends struts default intercep...

new 乙個物件時發生了什麼

var person function name var p new person boring 以上 在呼叫時,會變成如下 var p person name newobj.constructor.call newobj,name 3 return newobj 4 0 建立乙個新的物件,newo...

原來是這樣 C 中字串的記憶體分配與駐留池

剛開始學習c 的時候,就聽說clr對於string類有一種特別的記憶體管理機制 有時候,明明宣告了兩個string類的物件,但是他們偏偏卻指向同乙個例項。如下 string s1 hello string s2 hello s2和s1的實際值都是 hello bool same object s1 ...