6

なぜ :

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 の LDC は次を示します。

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

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

しかし、バイトsizeOfを示しています...2

ここで何が欠けていますか? short は実際に mem で何バイトかかりますか?

4 バイトまでのパディングがある状況について聞いたことがあります。その方が処理が速くなります。ここでもそうですか。

(syncRoot と GC ルート フラグ バイトは無視してください。2 対 4 について尋ねているだけです)

4

4 に答える 4

7

CLI 仕様は、スタック上で許可されるデータ型について非常に明示的です。短い 16 ビット整数はその 1 つではないため、このようなタイプの整数は、スタックにロードされるときに 32 ビット整数 (4 バイト) に変換されます。

パーティション III.1.1 には、すべての詳細が含まれています。

1.1 データ型

CTS は豊富な型システムを定義し、CLS は言語の相互運用性のために使用できるサブセットを指定しますが、CLI 自体ははるかに単純な型のセットを扱います。これらの型には、ユーザー定義の値型と組み込み型のサブセットが含まれます。「基本的な CLI タイプ」と総称されるサブセットには、次のタイプが含まれます。

  • int32完全な数値型 ( 、int64native int、および)のサブセットF
  • オブジェクト参照 ( O) は、参照されるオブジェクトのタイプを区別せずに行われます。
  • 指す型を区別しないポインタ型 (native unsigned intおよび)。&

オブジェクト参照とポインター型に value を割り当てることができることに注意してくださいnull。これは、CLI 全体でゼロ (すべてのビットがゼロのビット パターン) であると定義されています。

1.1.1 数値データ型

  • CLI は、数値型int32(4 バイトの符号付き整数)、int64(8 バイトの符号付き整数)、native int(ネイティブ サイズの整数)、およびF(ネイティブ サイズの浮動小数点数) でのみ動作します。ただし、CIL 命令セットを使用すると、追加のデータ型を実装できます。

  • 短い整数: 評価スタックは 4 バイトまたは 8 バイトの整数のみを保持しますが、他の場所 (引数、ローカル変数、静的変数、配列要素、フィールド) は 1 バイトまたは 2 バイトの整数を保持できます。スタック操作のために、bool 型と char 型はそれぞれ符号なしの 1 バイト整数と 2 バイト整数として扱われます。これらの場所からスタックにロードすると、次の方法で 4 バイト値に変換されます。

    • タイプ unsigned int8、unsigned int16、bool、および char のゼロ拡張。
    • 型 int8 および int16 の符号拡張。
    • 符号なし間接および要素ロードのゼロ拡張 ( ldind.u*ldelem.u*など);; と
    • 符号付き間接および要素ロードの符号拡張 ( ldind.i*ldelem.i*など)

整数、ブール値、および文字 ( stlocstfldstind.i1stelem.i2など) に格納すると、切り捨てられます。手順を使用してconv.ovf.*、この切り捨ての結果、元の値を正しく表していない値になったことを検出します。

[注: 短い (つまり、1 バイトと 2 バイトの) 整数はすべてのアーキテクチャで 4 バイトの数値としてロードされ、これらの 4 バイトの数値は常に 8 バイトの数値とは異なるものとして追跡されます。これにより、デフォルトの算術動作 (つまり、命令が実行されないconv場合conv.ovf) がすべての実装で同じ結果になることが保証されるため、コードの移植性が向上します。]

short 整数値を生成する変換命令は、実際にはint32(32 ビット) 値をスタックに残しますが、意味を持つのは下位ビットだけであることが保証されています (つまり、符号なし変換の場合は上位ビットがすべてゼロであり、符号拡張の場合は符号拡張です)。署名された変換)。div短整数演算の完全なセットを正しくシミュレートするには、 、remshr、比較および条件分岐命令の前に短整数への変換が必要です。

…等々。

憶測で言えば、この決定はおそらく、アーキテクチャーの単純化または速度 (あるいはその両方) のためになされたものです。最新の 32 ビットおよび 64 ビット プロセッサは、16 ビット整数よりも 32 ビット整数の方が効率的に機能します。また、2 バイトで表現できる整数はすべて 4 バイトでも表現できるため、この動作は合理的です。 .

4 バイトの整数ではなく 2 バイトの整数を使用することが本当に理にかなっているのは、実行速度/効率よりもメモリ使用量に関心がある場合だけです。その場合、おそらく構造体にパックされた、それらの値の全体が必要になります。そして、それはあなたが の結果を気にするときですMarshal.SizeOf

于 2013-07-07T11:39:55.240 に答える
5

利用可能な LDC 命令を見れば、何が起こっているかを簡単に知ることができます。使用できるオペランドの型は限られていることに注意してください。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() はobject型の引数を取るため、変数値をボックス化する必要があります。ボックス化された値は、型ハンドルによって値の型を識別し、System.Int16 を指します。Marshal.SizeOf() には、2 バイトかかることを知る組み込みの知識があります。

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

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

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

于 2013-07-07T12:41:28.237 に答える
1

CLR は、スタック上の 32 ビットおよび 64 ビットの整数でのみネイティブに機能します。答えは次の指示にあります。

box System.Int16

これは、値の型が Int16 としてボックス化されていることを意味します。C# コンパイラは、このボックス化を自動的に発行して Marshal.SizeOf(object) を呼び出します。これにより、ボックス化された値に対して GetType() が呼び出され、typeof(System.Int16) が返されます。

于 2013-07-07T11:47:00.733 に答える
1

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

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

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

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

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


1これは、IL が動作する仮想スタックであり、マシン コードが動作するスタックとは異なります。

于 2013-07-07T11:45:39.213 に答える