比較的大きな構造を作成している場合、それがメモリ内で占めるバイトをどのように計算できますか?
手動で行うこともできますが、構造体が十分に大きい場合はどうすればよいでしょうか。コードチャンクまたはアプリケーションはありますか?
sizeof
演算子または SizeOf
関数を使用できます。
これらのオプションにはいくつかの違いがあります。詳細については、参照リンクを参照してください。
とにかく、その関数を使用する良い方法は、次のようなジェネリックメソッドまたは拡張メソッドを持つことです。
static class Test
{
static void Main()
{
//This will return the memory usage size for type Int32:
int size = SizeOf<Int32>();
//This will return the memory usage size of the variable 'size':
//Both lines are basically equal, the first one makes use of ex. methods
size = size.GetSize();
size = GetSize(size);
}
public static int SizeOf<T>()
{
return System.Runtime.InteropServices.Marshal.SizeOf(typeof(T));
}
public static int GetSize(this object obj)
{
return System.Runtime.InteropServices.Marshal.SizeOf(obj);
}
}
構造体は、非常に長い間、コンピュータエンジニアリングにおいて厄介な動物でした。それらのメモリレイアウトは、ハードウェアに大きく依存します。それらを効率的にするために、CPUがメモリバス幅に合うようにバイトを多重化する必要なく、それらの値をすばやく読み書きできるように、それらのメンバーを整列させる必要があります。すべてのコンパイラーには、メンバーをパックする独自の戦略があり、多くの場合、例えば、CまたはC++プログラムの#pragma packディレクティブによって指示されます。
これは問題ありませんが、相互運用シナリオでは問題です。コードの1つのチャンクが、構造のレイアウトについて、別のコンパイラーによってコンパイルされた別のチャンクとは異なる仮定を行う場合があります。これは、相互運用プログラミングの.NETの祖父ソリューションであるCOMで確認できます。 COMはvery構造体の処理をサポートしていません。ネイティブオートメーションタイプとしてはサポートされていませんが、IRecordInfoインターフェイスを介した回避策があります。これにより、プログラムは、タイプライブラリ内の構造体の明示的な宣言を通じて、実行時にメモリレイアウトを発見できます。これは大丈夫ですが、非常に非効率的です。
.NET設計者は、この問題を解決するために非常に勇気があり、正しい決定を行いました。彼らは構造体のメモリレイアウトを完全に発見不可能にしました。メンバーのオフセットを取得するための文書化された方法はありません。そして、拡張によって、構造体のサイズを発見する方法はありません。みんなのお気に入りの答えは、Marshal.SizeOf()を使用することは、実際には解決策ではありません。これにより、構造体のサイズマーシャリング後が返されます。これは、Marshal.StructureToPtrを呼び出す前に、Marshal.AllocCoTaskMem()などに渡す必要があるサイズです。これにより、構造体に関連付けられている[StructLayout]属性に応じて、構造体のメンバーが配置および整列されます。この属性は(クラスの場合のように)構造体には必要ありません。ランタイムは、メンバーの宣言された順序を使用するデフォルトの属性を実装します。
レイアウトが見つからないことの非常に優れた副作用の1つは、CLRがそれを使ってトリックを実行できることです。構造体のメンバーをパックして整列すると、レイアウトはデータを格納しないホールを取得する可能性があります。パディングバイトと呼ばれます。レイアウトが検出できない場合、CLRは実際にパディングを使用できます。このような穴に収まるほど小さい場合は、メンバーを移動します。実際には、宣言された構造体のレイアウトで通常必要となるサイズよりも小さいの構造体を取得します。そして、特に、Marshal.SizeOf()は構造体サイズのwrong値を返しますが、大きすぎる値を返します。
要するに、プログラムで構造サイズの正確な値を取得する一般的な方法はありません。最善の策は、質問をしないことです。 Marshal.SizeOf()は、構造がblittableであると仮定して、推測を提供します。何らかの理由で正確な値が必要な場合は、構造タイプのローカル変数を宣言するメソッドの生成されたマシンコードを見て、そのローカル変数なしの同じメソッドと比較できます。スタックポインター調整の違い、メソッドの上部にある「sub esp、xxx」命令が表示されます。もちろん、これはアーキテクチャに依存します。通常、64ビットモードではより大きな構造になります。
参照型であるフィールドまたはプロパティを含まないユーザー定義の構造体には、キーワード sizeof()
を使用するか、または Marshal.SizeOf(Type)
または Marshal.SizeOf(object)
シーケンシャルまたは明示的な型の構造体または構造体 layout のアンマネージサイズを取得します。
私は [〜#〜] cil [〜#〜] ( 。NET のアセンブリ言語)で小さな小さなライブラリを書いて、利用できない機能を公開しましたC#で。 sizeof
命令を開始しました。
C#のsizeof
演算子とは大きく異なります。基本的に、それはパディングとすべてを含む構造体(またはいくつかの最適化で面白い動作をする参照型)のサイズを取得します。したがって、T
の配列を作成する場合、sizeofを使用して、各配列要素間の距離をバイト単位で決定できます 。また、完全に検証可能で管理されたコードでもあります。 Mono にはバグがあり(3.0より前?)、参照タイプのサイズが誤って報告され、参照タイプを含む構造に拡張されることに注意してください。
とにかく、BSDライセンスライブラリ(およびCIL) BitBucketから をダウンロードできます。また、いくつかのサンプルコードといくつかの詳細 私のブログ も確認できます。
0。サンプルコードの場合:
_using System;
using System.Diagnostics;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
_
1。デモ構造体
_[Serializable, StructLayout(LayoutKind.Sequential, Pack = 128)]
public struct T
{
public int a;
public byte b;
public int c;
public String d;
public short e;
};
_
2。マネージポインターの減算:
_/// Return byte-offset between managed addresses of struct instances 'hi' and 'lo' public static long IL<T1,T2>.RefOffs(ref T1 hi, ref T2 lo) { ... }
_
_public static class IL<T1, T2>
{
public delegate long _ref_offs(ref T1 hi, ref T2 lo);
public static readonly _ref_offs RefOffs;
static IL()
{
var dm = new DynamicMethod(
Guid.NewGuid().ToString(),
typeof(long),
new[] { typeof(T1).MakeByRefType(), typeof(T2).MakeByRefType() },
typeof(Object),
true);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Sub);
il.Emit(OpCodes.Conv_I8);
il.Emit(OpCodes.Ret);
RefOffs = (_ref_offs)dm.CreateDelegate(typeof(_ref_offs));
}
};
_
3。管理された内部構造体レイアウトを明らかにする:
_static class demonstration
{
/// Helper thunk that enables automatic type-inference from argument types
static long RefOffs<T1,T2>(ref T1 hi, ref T2 lo) => IL<T1,T2>.RefOffs(ref hi, ref lo);
public static void Test()
{
var t = default(T);
var rgt = new T[2];
Debug.Print("Marshal.Sizeof<T>: {0,2}", Marshal.SizeOf<T>());
Debug.Print("&rgt[1] - &rgt[0]: {0,2}", RefOffs(ref rgt[1], ref rgt[0]));
Debug.Print("int &t.a {0,2}", RefOffs(ref t.a, ref t));
Debug.Print("byte &t.b {0,2}", RefOffs(ref t.b, ref t));
Debug.Print("int &t.c {0,2}", RefOffs(ref t.c, ref t));
Debug.Print("String &t.d {0,2}", RefOffs(ref t.d, ref t));
Debug.Print("short &t.e {0,2}", RefOffs(ref t.e, ref t));
}
};
_
4。結果と考察
StructLayout(..., Pack)
設定は、_struct T
_の宣言に次の値のいずれかで追加できます:{0、1、2、4、8、16 32、64、128}。 Pack
が指定されていない場合、または_Pack=0
_と同等の場合のデフォルト値は、パッキングを_IntPtr.Size
_(x86では_4
_、x64では_8
_)に等しく設定します。
上記のプログラムを実行した結果は、Pack
値が_Marshal.SizeOf
_によって報告されたmarshaling sizeにのみ影響し、単一のT
メモリイメージの実際のサイズには影響しないことを示しています、物理的に隣接するインスタンス間のバイトオフセットと見なされます。テストコードは、rgtに割り当てられた診断マネージ配列_new T[2]
_を介してこれを測定します。
_
========= x86 ==========
_ _========= x64 ==========
__
-------- Pack=1 --------
_ _-------- Pack=1 --------
_Marshal.Sizeof(): 15
Marshal.Sizeof(): 19
_&rgt[1] - &rgt[0]: 16
_ _&rgt[1] - &rgt[0]: 24
__
-------- Pack=2 --------
_ _-------- Pack=2 --------
_Marshal.Sizeof(): 16
Marshal.Sizeof(): 20
_&rgt[1] - &rgt[0]: 16
_ _&rgt[1] - &rgt[0]: 24
__
--- Pack=4/0/default ---
_ _-------- Pack=4 --------
_Marshal.Sizeof(): 20
Marshal.Sizeof(): 24
_&rgt[1] - &rgt[0]: 16
_ _&rgt[1] - &rgt[0]: 24
_
_-------- Pack=8 --------
_ _--- Pack=8/0/default ---
_Marshal.Sizeof(): 20
Marshal.Sizeof(): 32
_&rgt[1] - &rgt[0]: 16
_ _&rgt[1] - &rgt[0]: 24
__
-- Pack=16/32/64/128 ---
_ _-- Pack=16/32/64/128 ---
_Marshal.Sizeof(): 20
Marshal.Sizeof(): 32
_&rgt[1] - &rgt[0]: 16
_ _&rgt[1] - &rgt[0]: 24
_
前述のように、アーキテクチャごとに(x86、x64) 、管理対象フィールドのレイアウトは、Pack
の設定に関係なく一貫しています。上記のコードで報告されているように、32ビットモードと64ビットモードの実際の管理フィールドオフセットを次に示します。
_
┌─offs─┐
_
_field type size x86 x64
_
_===== ====== ==== === ===
_
_a int 4 4 8
_
_b byte 1 14 18
_
_c int 4 8 12
_
_d String 4/8 0 0
_
_e short 2 12 16
_
この表で最も重要なことは、( Hansによって言及された のように)報告されたフィールドオフセットは、宣言の順序に関して単調ではないということです。 ValueType
インスタンスのフィールドは常に並べ替えられ、すべての参照型フィールドが最初に表示されます。 String
フィールドdがオフセット0にあることがわかります。
さらに並べ替えると、フィールドの順序が最適化され、本来なら無駄になる内部パディングの超過が共有されます。これは、byte
フィールドbで確認できます。これは、2番目に宣言されたフィールドから最後に移動されました。
当然、前の表の行を並べ替えることで、。NETValueType
の実際の内部管理レイアウトを明らかにできます。管理された参照(_String d
_)を含み、したがってnon-blittable /の例のstruct T
にもかかわらず、このレイアウトを取得できることに注意してください:
_
============= x86 ============
_ _============= x64 ============
_
_field type size offs end
_ _field type size offs end
_
_===== ====== ==== ==== ===
_ _===== ====== ==== ==== ===
_
_d String 4 0 … 4
_ _d String 8 0 … 8
_
_a int 4 4 … 8
_ _a int 4 8 … 12
_
_c int 4 8 … 12
_ _c int 4 12 … 16
_
_e short 2 12 … 14
_ _e short 2 16 … 18
_
_b byte 1 14 … 15
_ _b byte 1 18 … 19
__
internal padding: 1 15 … 16
_ _internal padding: 5 19 … 24
__
x86 managed total size: 16
_ _x64 managed total size: 24
_
以前、隣接するインスタンス間のバイトオフセットの差を計算することにより、単一のマネージ構造体インスタンスのサイズを決定しました。これを考慮に入れると、前の表の最後の行は、[〜#〜] clr [〜#〜]が内部的に適用されるパディングを簡単に示しています例のstruct T
の終わり。もちろん、この内部パディングはCLRによって固定されており、完全に制御できないことに注意してください。
5。コーダ
完全を期すため、この最後の表は、合成されるパディングの量を示していますon-the-flymarshaling。場合によっては、このMarshal
のパディングは、内部で管理されているサイズと比較してマイナスになることがあります。たとえば、x64のT
の内部管理サイズは24バイトですが、マーシャリングによって生成される構造体は、_Pack=1
_または_Pack=2
_です。
_
pack size offs end
_ _pack size offs end
_
_============= ==== ==== ===
_ _============= ==== ==== ===
_
_1 0 15 … 15
_ _1 0 19 … 19
_
_2 1 15 … 16
_ _2 1 19 … 20
_
_4/8/16/32/64… 5 15 … 20
_ _4/8/16/32/64… 5 19 … 24
_
。NET Core では、最近追加されたsizeof
クラスを介してUnsafe
CIL命令が公開されました。 System.Runtime.CompilerServices.Unsafe
パッケージへの参照を追加し、これを実行します。
int size = Unsafe.SizeOf<MyStruct>();
参照タイプでも機能します(コンピューターのアーキテクチャに応じて4または8を返します)。
使用したい System.Runtime.InteropServices.Marshal.SizeOf() :
struct s
{
public Int64 i;
}
public static void Main()
{
s s1;
s1.i = 10;
var s = System.Runtime.InteropServices.Marshal.SizeOf(s1);
}
System.Runtime.InteropServices.Marshal.SizeOf()
を使用して、サイズをバイト単位で取得することもできます。