2013-07-07 29 views
6

爲什麼:在.NET中的Int16字節容量?

short a=0; 
Console.Write(Marshal.SizeOf(a)); 

顯示2

但是,如果我看到的IL代碼,我看到:

/*1*/ IL_0000: ldc.i4.0  
/*2*/ IL_0001: stloc.0  
/*3*/ IL_0002: ldloc.0  
/*4*/ IL_0003: box   System.Int16 
/*5*/ IL_0008: call  System.Runtime.InteropServices.Marshal.SizeOf 
/*6*/ IL_000D: call  System.Console.Write 

最不發達國家的線#1表示:

推0作爲int32

所以必須有4個字節佔用。

sizeOf顯示2字節...

缺少什麼我在這裏? mem實際佔用多少字節?

我聽說過有一個填充爲4字節的情況,所以處理速度會更快。這裏也是這種情況嗎?

(請忽略SyncRoot上和GC根標誌字節我只是問2比4)

+4

當你將它嵌入到一個更大的結構中時,例如在一個數組中,內存中的大小隻是一個有意義的概念。當你有一個局部變量時,它通常佔用一個完整的寄存器(AMD64上的64位),即使它只是一個字節。 C#編譯器在內部大多數情況下使用Int32,只要觀察到的行爲與Int16的行爲相匹配,它就在其權限內。 – CodesInChaos

+0

@CodesInChaos所以它需要32位的4個字節?如果是這樣,爲什麼sieOf顯示2? –

+1

大小是2,因爲它是這樣定義的,2個字節就足夠了。但是如果你有一個局部變量,它需要編譯器/ JITer需要的字節數。只要程序的行爲不受影響,他們就可以自由地做任何他們喜歡的事情。在方法運行期間,單個局部變量通常存儲在不同的地方。在你的情況下,他們甚至可能完全消除'a',而是簡單地使用常量'2'。 – CodesInChaos

回答

5

通過查看available LDC instructions可以很容易地知道發生了什麼。注意可用的操作數類型的有限集合,有沒有版本可用,加載一個short類型的常量。只要int,long,float和double。這些限制在其他地方是可見的,例如Opcodes.Add指令同樣受到限制,不支持添加其中一個較小類型的變量。

IL指令集是非常有意地這樣設計的,它反映了一個簡單的32位處理器的功能。想到的處理器類型是RISC類型,他們在九十歲時有他們的乾草日。大量的32位cpu寄存器只能處理32位整數和IEEE-754浮點類型。英特爾x86內核不是一個很好的例子,雖然很常用,但它是一種CISC設計,實際上支持在8位和16位操作數上加載和執行算術運算。但這更像是一次歷史性的事故,它使機器翻譯程序變得簡單,從8位8080和16位8086處理器開始。但是這種能力並不是免費的,操縱16位值實際上會花費額外的CPU週期。

使IL與32位處理器功能良好匹配顯然使得實現抖動的人的工作更加簡單。存儲位置的大小可能仍然較小,但只有加載,存儲和轉換需要支持。只有在需要的時候,你的'a'變量是一個局部變量,無論如何它都佔用了堆棧幀或者cpu寄存器的32位。只有內存需要被截斷到正確的大小。

在代碼片段中沒有其他不明確的地方。因爲Marshal.SizeOf()需要參數類型爲的對象,所以需要將變量值加框。盒裝值通過類型句柄標識值的類型,它將指向System.Int16。 Marshal.SizeOf()具有知道它需要2個字節的內置知識。

這些限制確實反映了C#語言並導致不一致。這種編譯錯誤的永遠befuddles和惹惱C#程序員:

byte b1 = 127; 
    b1 += 1;   // no error 
    b1 = b1 + 1;  // error CS0266 

哪家的IL限制,因此,不存在添加運算符以字節操作數。在這種情況下,需要將它們轉換爲下一個較大的兼容類型,即int。所以它適用於32位RISC處理器。現在有一個問題,32位的結果需要被重新轉換成一個只能存儲8位的變量。 C#語言在第一作業中使用錘子本身,但在第二作業中不合邏輯地需要使用錘子。

+0

Hans,_「int,long,float和double」_ - 唯一可以表示純正確十進制數的類型('decimal')是什麼?這個怎麼樣?謝謝。 (p.s.我喜歡答案結構:歷史,現在,例子。) –

+0

CLR沒有System.Decimal的專門知識,只有編譯器。對於CLR,它只是一個普通的結構類型。 –

7

的CLI規範是關於允許在棧中的數據類型非常明確的。短16位整數不是其中之一,所以這些類型的整數在加載到堆棧時會轉換爲32位整數(4個字節)。

分區III.1.1包含所有的細節:

1.1數據類型

雖然CTS定義了豐富的類型系統和CLS指定可以用於語言 一個子集互操作性,CLI本身處理更簡單的一組類型。這些類型包括用戶定義的值 類型和內置類型的子集。子集,統稱爲 「基本CLI類型」,包含 以下幾種類型:

  • 全數字類型的子集(int32int64native intF)。
  • 對象引用(O)不區分引用的對象的類型。
  • 指針類型(native unsigned int&)與指向的類型沒有區別。

請注意,對象引用和指針類型可以分配值null。這在整個CLI中定義爲零(全比特零位模式)。

1.1。1種數字數據類型

  • 的CLI僅在數字類型int32(4字節帶符號的整數),int64(8字節 符號整數),native int(天然大小整數)進行操作,和F(原始大小的浮點數 數字)。但是,CIL指令集允許實現其他數據類型:

  • 短整數:評估堆棧僅保存4或8字節整數,但其他位置 (參數,局部變量,靜態,數組元素,字段)可以保存1或2字節的整數。對於堆棧操作的 目的,bool和char類型分別爲 ,分別視爲無符號1字節和2字節整數。從這些位置裝載到所述 堆棧通過將它們轉換成4字節值:

    • 零延伸,用於類型無符號INT8,無符號INT16,布爾和焦炭;
    • int8和int16類型的符號擴展;
    • 對於未經簽名的間接和元素加載(ldind.u*,ldelem.u*等)進行零擴展;和
    • 登錄延伸,用於簽名的間接和元件的負載(ldind.i*ldelem.i*等)

存儲到整數,布爾值,和字符(stlocstfldstind.i1stelem.i2等)截斷。使用conv.ovf.*指令來檢測此截斷何時會導致無法正確表示原始值的值。

[注意:所有體系結構中的短整型(即1字節和2字節)整數都是4字節數字,並且這些4字節數字總是被跟蹤爲不同於8字節數字。這通過確保默認算術行爲(即,當不執行convconv.ovf指令)將具有在所有的實現相同的結果可以幫助的代碼的可移植性。],其產生短整數值

轉換指令實際上留下一個int32( 32位)值,但保證只有低位具有含義(即無符號轉換的更高有效位全爲零或帶符號轉換的符號擴展)。爲了正確地模擬整組短整數運算,在div,rem,,比較 和條件分支指令之前需要轉換爲短整數。

......等等。

推測性地說,這個決定很可能是建築簡單或速度(或可能是兩者)。現代的32位和64位處理器對於32位整數可以比對16位整數更有效地工作,並且由於所有可以用2個字節表示的整數也可以用4個字節表示,所以這種行爲是合理的。

使用2字節整數而不是4字節整數的唯一時間是如果你更關心內存使用情況,而不是執行速度/效率。在這種情況下,你需要有一大堆這些值,可能是打包成一個結構。那就是當你關心Marshal.SizeOf的結果時。

1

C#語言規範定義了程序應該如何工作。只要行爲是正確的,它並沒有說明如何實現這一點。如果你問一個short的大小,你總會得到2

實際上C#編譯爲CIL,其中小於32位的整數類型表示爲堆棧上的32位整數。

然後,JITer將其重新映射到任何適合目標硬件的內容,通常是堆棧或寄存器中的一塊內存。

只要這些轉換沒有變化可觀察到的行爲他們是合法的。

實際上局部變量的大小在很大程度上是不相關的,重要的是數組的大小。一百萬個short的陣列通常將佔用2MB。


這是一個虛擬的堆棧中的IL操作上,這是從堆棧機器代碼的操作上不同。

1

CLR只能在堆棧上使用32位和64位整數。答案在於:

box System.Int16 

這意味着值類型被裝箱爲Int16。 C#編譯器自動發出這個裝箱函數來調用Marshal.SizeOf(object),後者又調用裝箱值的GetType(),它返回typeof(System.Int16)。