2013-07-07 14 views
6

はなぜライン#1でInt16バイトの容量in.net?</p> <pre><code>short a=0; Console.Write(Marshal.SizeOf(a)); </code></pre> <p>が<code>2</code></p> <p>示ししかし、私は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 

LDCが示す:

プッシュ0をスタックにint32としてスタックします。

したがって、4バイトが占有されている必要があります。

しかしsizeOf2バイトを示しています...

私はここで何をしないのですか?どのくらいのバイトが実際にmemを取るか?

4バイトまでのパディングがあるので、対処するのが速いと聞いたことがあります。それもここのケースですか?

+4

メモリ内のサイズは、配列などのより大きな構造に埋め込む場合にのみ意味のある概念です。ローカル変数を持っているときは、それが単なる1バイトであっても、通常は完全なレジスタ(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の種類であり、彼らは19世紀の彼らの乾草期を迎えました。 32ビット整数とIEEE-754浮動小数点型のみを操作できる32ビットCPUレジスタがたくさんあります。 Intel x86コアは良い例ではありませんが、非常に一般的に使用されていますが、実際には8ビットと16ビットのオペランドの読み込みと演算をサポートするCISCデザインです。しかしこれは歴史的な事故であり、8ビットの8080と16ビットの8086プロセッサーから始まるプログラムの機械的な翻訳を簡単にしました。しかし、このような機能は無償で提供されるわけではなく、16ビットの値を操作すると実際に余分なCPUサイクルがかかります。

ILを32ビットプロセッサの機能とよくマッチさせることで、明らかにジッタを実装するユーザーの仕事がはるかに簡単になります。ストレージの場所はまだ小さくてもかまいませんが、ロード、ストア、および変換のみをサポートする必要があります。そして、必要なときだけ、あなたの 'a'変数はローカル変数です。スタックフレームまたはCPUレジスタに32ビットを占有する変数です。メモリへのストアのみを適切なサイズに切り捨てる必要があります。

そうでなければ、コードスニペットにあいまいさはありません。 Marshal.SizeOf()はオブジェクトの型の引数を取るため、変数値を囲む必要があります。ボックス化された値は、型ハンドルによって値の型を識別し、System.Int16を指します。 Marshal.SizeOf()には、2バイトかかることを知るビルトイン知識があります。

これらの制限はC#言語に反映され、矛盾を引き起こします。コンパイルエラーのこの種のは、永遠のC#プログラマをbefuddlesと悩ま:

IL制限の結果である
byte b1 = 127; 
    b1 += 1;   // no error 
    b1 = b1 + 1;  // error CS0266 

、バイトのオペランドを取り全く追加演算子はありません。この場合は、次に大きい互換性のあるタイプintに変換する必要があります。したがって、32ビットRISCプロセッサで動作します。今問題がありますが、32ビットのの結果を8ビットしか格納できない変数に戻す必要があります。 C#言語では、最初の割り当てでハンマー自体が適用されますが、2番目の割り当てでは非形式的にキャストハンマーが必要です。

+0

Hans、_ "int、long、float and double" _純粋で正確な10進数( 'decimal')を表す唯一の型はどうでしょうか?ここにはどうやって収まるの?ありがとう。 (p.s.私は答えの構造が好きです:履歴、現在、例) –

+0

CLRにはSystem.Decimalに関する特別な知識はなく、コンパイラだけが行います。 CLRには単純な構造型です。 –

7

(SyncRootのと私はちょうど約2を求めているGCルートフラグバイト対4を無視してください)CLI仕様は、スタック上にあることを許可されているデータ型について非常に明示的です。短い16ビット整数はそのうちの1つではないので、スタックにロードされるとき、そのような型の整数は32ビットの整数(4バイト)に変換されます。 CTSは、豊富な型システムを定義し、CLSは、言語 のために使用することができるサブセットを指定しますが

1.1データ型

パーティションIII.1.1は細部のすべてが含まれています相互運用性のために、CLI自体ははるかに単純なタイプのセットを扱います。これらのタイプには、ユーザ定義値 タイプと組込みタイプのサブセットが含まれます。総称して「基本的なCLIタイプ」と呼ばれるサブセットは、 次の種類が含まれています

  • 完全な数値型のサブセット(int32int64native int、およびF)。
  • 参照されるオブジェクトの種類を区別せずにオブジェクト参照(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、BOOLとcharのゼロ延びます。
    • int8およびint16型の符号拡張。
    • 符号なし間接および要素ロード(ldind.u*,ldelem.u*など)のゼロ拡張。そして
    • 署名さ間接素子の負荷(ldind.i*ldelem.i*、等)

(等、stelem.i2stlocstfldstind.i1)整数、ブール値、および文字に記憶するための符号拡張切り詰めます。 conv.ovf.*命令を使用して、この切り捨ての結果が元の値を正しく表していない値になったときを検出します。

[注:短い(つまり、1バイトおよび2バイト)整数はすべてのアーキテクチャで4バイトの数字としてロードされ、これらの4バイトの数字は常に8バイトの数字とは別のものとして追跡されます。これはデフォルトの演算動作は(すなわち、conv又はconv.ovf命令が実行されていない場合)は、すべての実装に同一の結果を有するであろうことを保証することによって、コードの移植を助ける。】短整数値を得

変換命令が実際に(int32を残し32ビット)の値を使用しますが、下位ビットのみが意味を持ちます(つまり、符号なし変換の場合は上位ビットがすべて0、符号付き変換の場合は符号拡張)。短い整数演算の完全なセットを正しくシミュレートするには、div,rem,shr、比較 および条件付き分岐命令の前に、短い整数への変換が必要です。

&hellip;などです。

この決定はおそらく、アーキテクチャの簡素化またはスピード(あるいはその両方)のために行われました。最新の32ビットおよび64ビットプロセッサは、32ビット整数で16ビット整数よりも効果的に動作し、2バイトで表現できるすべての整数も4バイトで表現できるため、この動作は妥当です。

実行速度/効率よりもメモリ使用量に関心がある場合は、4バイトの整数とは対照的に2バイト整数を使用するのが実際には賢明でしょう。そして、その場合、おそらく構造体に詰め込まれたこれらの値の束を持つ必要があります。そして、それはMarshal.SizeOfの結果を気にするときです。

1

C#言語仕様では、プログラムの動作方法を定義しています。振る舞いが正しい限り、これを実装する方法は述べていません。 shortのサイズを尋ねる場合は、常に2が得られます。

実際にC#はCILにコンパイルされます。ここでは、32ビットより小さい整数型は、スタック上に32ビット整数として表されます。。

次に、JITerはターゲットハードウェアに適したもの(通常はスタック上のメモリまたはレジスタ)に再度マップし直します。

これらの変換がいずれも変化しない限り、の観察可能な動作は合法です。

実際には、ローカル変数のサイズはほとんど関係がありません。重要なのは配列のサイズです。 100万の配列が通常2 MBを占めます。


これはILは、マシンコード演算のスタックと異なっている、上で動作する仮想スタックです。

1

CLRはスタック上に32ビットと64ビットの整数でのみ動作します。答えはこの命令にあります。

box System.Int16 

つまり、値のタイプはInt16として囲まれています。 C#コンパイラは自動的にMarshal.SizeOf(object)を呼び出すためにこのボクシングを発生し、typeof(System.Int16)を返すボックス化された値でGetType()を呼び出します。

関連する問題

 関連する問題