当子结构具有LayoutKind.Explicit时,不遵循LayoutKind.Sequential

Rol*_*kas 13 .net c# unsafe structlayout layoutkind.explicit

运行此代码时:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我希望这个输出在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

我的问题:我错过了什么?或者这是一个错误?
我发现了一个类似的问题:
如果struct包含DateTime字段,为什么LayoutKind.Sequential的工作方式不同?
但在我的情况下,即使子结构的属性发生更改,布局也会更改,而不会对数据类型进行任何更改.所以它看起来不像是一种优化.除此之外,我想指出另一个问题仍然没有答案.
在另一个问题中,他们提到使用编组时布局得到尊重.我自己没有测试过,但我想知道为什么不安全代码不考虑布局,因为所有相关属性似乎都已到位?除非完成编组,否则文档是否提到忽略这些属性?为什么?
考虑到这一点,我是否可以期望LayoutKind.Explicit可靠地处理不安全的代码?
此外,文档提到了保持结构符合预期布局的动机:

为了减少与自动值相关的布局相关问题,C#,Visual Basic和C++编译器为值类型指定顺序布局.


但这个动机显然不适用于不安全的代码?

Han*_*ant 11

从用于LayoutKind枚举的MSDN Library文章:

根据StructLayoutAttribute.Pack字段的设置,显式控制对象在非托管内存中的每个成员的精确位置.每个成员必须使用FieldOffsetAttribute来指示该字段在类型中的位置.

相关的短语突出显示,这个程序没有发生,指针仍然非常取消引用托管内存.

是的,您所看到的与结构包含DateTime类型的成员时发生的情况相同,DateTime是一种应用了[StructLayout(LayoutKind.Auto)]的类型.确定布局的CLR中的字段编组器代码也努力为受管理的结构设计遵循LayoutKind.Sequential.但如果遇到任何与此目标冲突的成员,它将很快放弃而不会尖叫.一个本身不是顺序的结构就足够了.您可以在SSCLI20源代码 src/clr/vm/fieldmarshaler.cpp中查看此操作,搜索fDisqualifyFromManagedSequential

这将使其切换到自动布局,与应用于类的布局规则相同.它重新排列字段以最小化成员之间的填充.净效应是所需的内存量更小.在"Bool"成员之后有7个字节的填充,未使用的空间使"Long"成员对齐到8的倍数的地址.当然,它通过使long成为布局中的第一个成员来修复它.

因此,而不是使用/*offset - size*/annotated的显式布局:

        public int A;        /*  0 - 4 */
        public int B;        /*  4 - 4 */
        public bool Bool;    /*  8 - 1 */
        // padding           /*  9 - 7 */
        public long Long;    /* 16 - 8 */
        public Explicit C;   /* 24 - 8 */
                     /* Total:  32     */ 
Run Code Online (Sandbox Code Playgroud)

它提出:

        public long Long;    /*  0 - 8 */
        public int A;        /*  8 - 4 */
        public int B;        /* 12 - 4 */
        public bool Bool;    /* 16 - 1 */
        // padding           /* 17 - 3 */
        public Explicit C;   /* 20 - 8 */
                     /* Total:  28     */ 
Run Code Online (Sandbox Code Playgroud)

轻松保存4个字节的内存.64位布局需要额外的填充以确保长整数存储在数组中时仍然对齐.这些都是高度无证的,可能会发生变化,请务必永远不要依赖托管内存布局.只有Marshal.StructureToPtr()可以给你一个保证.