2

質問

別の( )であるフィールドを持つSAを使用して、構造体 ( ) を構築しようとしました。[StructLayout(LayoutKind.Explicit)]structSB

最初[StructLayout(LayoutKind.Explicit)]: 他の構造体を なしで宣言できることに驚きましたが、SAでは、すべてのフィールドにが必要[FieldOffset(0)]です。あまり意味がありません。

  • これはコンパイラの警告/エラーの抜け穴ですか?

2 番目: のすべての参照 ( object) フィールドSBが の前に移動されているようですSB

  • この動作はどこかで説明されていますか?
  • 実装依存ですか?
  • 実装に依存しているとどこかで定義されていますか?:)

:これを本番コードで使用するつもりはありません。私は主に好奇心からこの質問をします。

実験

// No object fields in SB
// Gives the following layout (deduced from experimentation with the C# debugger):

// | f0 | f4 and i | f8 and j | f12 and k | f16 |

[StructLayout(LayoutKind.Explicit)]
struct SA {
    [FieldOffset(0)] int f0;
    [FieldOffset(4)] SB sb;
    [FieldOffset(4)] int f4;
    [FieldOffset(8)] int f8;
    [FieldOffset(12)] int f12;
    [FieldOffset(16)] int f16;
}
struct SB { int i; int j; int k; }

// One object field in SB
// Gives the following layout:

// | f0 | f4 and o1 | f8 and i | f12 and j | f16 and k |

// If I add an `object` field after `j` in `SB`, i *have* to convert
// `f4` to `object`, otherwise I get a `TypeLoadException`.
// No other field will do.

[StructLayout(LayoutKind.Explicit)]
struct SA {
    [FieldOffset(0)] int f0;
    [FieldOffset(4)] SB sb;
    [FieldOffset(4)] object f4;
    [FieldOffset(8)] int f8;
    [FieldOffset(12)] int f12;
    [FieldOffset(16)] int f16;
}
struct SB { int i; int j; object o1; int k; }

// Two `object` fields in `SB`
// Gives the following layout:

// | f0 | f4 and o1 | f8 and o2 | f12 and i | f16 and j | k |

// If I add another `object` field after the first one in `SB`, i *have* to convert
// `f8` to `object`, otherwise I get a `TypeLoadException`.
// No other field will do.

[StructLayout(LayoutKind.Explicit)]
struct SA {
    [FieldOffset(0)] int f0;
    [FieldOffset(4)] SB sb;
    [FieldOffset(4)] object f4;
    [FieldOffset(8)] object f8;
    [FieldOffset(12)] int f12;
    [FieldOffset(16)] int f16;
}
struct SB { int i; int j; object o1; object o2; int k; }
4

2 に答える 2

5

これはコンパイラの警告/エラーの抜け穴ですか?

いいえ、何も問題ありません。フィールドはオーバーラップできます。これが、そもそも LayoutKind.Explicit が存在する理由です。それ以外の場合は C# でサポートされていない、アンマネージ コードで共用体と同等のものを宣言できます。構造体宣言で [FieldOffset] の使用を突然停止することはできません。ランタイムは、構造体のすべてのメンバーで使用することを要求します。技術的には必要ありませんが、間違った仮定を避けるための単純な要件です。

SB のすべての参照 (オブジェクト) フィールドが移動されているようです

はい、これは正常です。CLR は、文書化されておらず、発見できない方法でオブジェクトをレイアウトします。使用する正確なルールは文書化されておらず、変更される可能性があります。また、異なるジッターに対しても繰り返されません。オブジェクトがマーシャリングされるか、Marshal.StructureToPtr() が呼び出されるか、pinvoke マーシャラーによって暗黙的に行われるまで、レイアウトは予測可能になりません。正確なレイアウトが重要になるのはこれだけです。この動作の根拠については、この回答で書きました。

于 2013-03-13T18:28:06.710 に答える
1

最初の質問に対する答えはノーです。コンパイラのエラー報告に抜け穴やバグはありません。明示的なレイアウトを開始すると、コンパイラは、(制限内で - 以下を参照してください) 何をしているかを知っていると想定します。ある構造を別の構造の上に重ねるように指示しました。コンパイラは、オーバーレイしている構造が明示的にレイアウトされていないことを気にしません (気にする必要もありません)。

コンパイラ気にした場合、明示的にレイアウトされていない型をオーバーレイすることはできません。つまり、一般的なケースではユニオンを実行できませんでした。たとえば、 aDateTimeと aを重ねてみることを考えてみましょうlong:

[StructLayout(LayoutKind.Explicit)]
struct MyUnion
{
    [FieldOffset(0)]
    public bool IsDate;
    [FieldOffset(1)]
    public DateTime dt;
    [FieldOffset(1)]
    public long counter;
}

DateTime明示的にレイアウトされていない限り、それはコンパイルされません。おそらくあなたが望むものではありません。

明示的にレイアウトされた構造に参照型を配置する限り、結果は...おそらく期待したものではありません。たとえば、次の単純なビットを考えてみましょう。

struct MyUnion
{
    [FieldOffset(0)]
    public object o1;
    [FieldOffset(0)]
    public SomeRefType o2;
}

それは型の安全性に大きく違反しています。コンパイルされた場合 (そうなる可能性は非常に高い)、使用しようとすると TypeLoadException で終了します。

コンパイラは、可能な限りタイプ セーフに違反しないようにします。コンパイラがこれらの属性を処理して構造をレイアウトする方法を認識しているかどうか、または生成された MSIL を介してレイアウト情報をランタイムに渡すだけかどうかはわかりません。コンパイラが特定のレイアウトを許可したが、ランタイムが TypeLoadException で爆撃された 2 番目の例を考えると、おそらく後者です。

[structlayout.explicit reference types] を Google で検索すると、興味深い議論がいくつか見つかります。明示的な構造体で複数の CLR 参照フィールドを互いにオーバーレイするを参照してください。、 例えば。

于 2013-03-13T18:25:15.043 に答える