このコードを実行すると:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace StructLayoutTest
{
class Program
{
unsafe static void Main()
{
Console.WriteLine(IntPtr.Size);
Console.WriteLine();
Sequential s = new Sequential();
s.A = 2;
s.B = 3;
s.Bool = true;
s.Long = 6;
s.C.Int32a = 4;
s.C.Int32b = 5;
int* ptr = (int*)&s;
Console.WriteLine(ptr[0]);
Console.WriteLine(ptr[1]);
Console.WriteLine(ptr[2]);
Console.WriteLine(ptr[3]);
Console.WriteLine(ptr[4]);
Console.WriteLine(ptr[5]);
Console.WriteLine(ptr[6]);
Console.WriteLine(ptr[7]); //NB!
Console.WriteLine("Press any key");
Console.ReadKey();
}
[StructLayout(LayoutKind.Explicit)]
struct Explicit
{
[FieldOffset(0)]
public int Int32a;
[FieldOffset(4)]
public int Int32b;
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct Sequential
{
public int A;
public int B;
public bool Bool;
public long Long;
public Explicit C;
}
}
}
x86 と x64 の両方でこの出力が期待されます:
4 または 8 (x86 または x64 に応じて)
2
3
1
6
0
4
5
ゴミ
x86で代わりに得られるもの:
4
6
0
2
3
1
4
5
ゴミ
x64で代わりに得られるもの:
8
6
0
2
3
1
0
4
5
詳細:
- LayoutKind.Explicit および FieldOffset 属性を削除すると、問題は解決します。
- Bool フィールドを削除すると、問題が解決します。
- Long フィールドを削除すると、問題が解決します。
- x64 では、Pack=4 属性パラメーターも無視されているように見えることに注意してください。
これは、.Net3.5 および .Net4.0 にも適用されます。
私の質問:何が欠けていますか?それともこれはバグですか?
同様の質問を見つけました:
構造体に DateTime フィールドが含まれている場合、LayoutKind.Sequential の動作が異なるのはなぜですか?
しかし、私の場合、サブ構造体の属性が変更されても、データ型を変更せずにレイアウトが変更されます。したがって、最適化のようには見えません。それに加えて、他の質問がまだ答えられていないことを指摘したいと思います。
その他の質問では、マーシャリングを使用するときにレイアウトが尊重されると述べています。私はそれを自分でテストしていませんが、関連するすべての属性が適切に配置されているように見えるのに、安全でないコードに対してレイアウトが尊重されないのはなぜでしょうか? マーシャリングが行われない限り、これらの属性は無視されるとドキュメントのどこかに記載されていますか? なんで?
これを考慮すると、安全でないコードに対して LayoutKind.Explicit が確実に機能することを期待できますか?
さらに、ドキュメントには、構造体を予想されるレイアウトで保持する動機について言及されています。
Auto 値に関連するレイアウト関連の問題を軽減するために、C#、Visual Basic、および C++ コンパイラは、値の型に Sequential レイアウトを指定します。
しかし、この動機はアンセーフ コードには当てはまらないようです。