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

本文关键字:LayoutKind 顺序 Explicit 结构 | 更新日期: 2023-09-27 18:11:51

运行此代码时:

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时,问题就消失了。显式和FieldOffset属性。
-当我删除Bool字段时,问题就消失了。
-当我删除Long字段时,问题就消失了。
-注意,在x64上,似乎Pack=4属性参数也被忽略了?

这适用于。net 3.5和。net 4.0

我的问题:我错过了什么?还是这是个bug?我发现了一个类似的问题:
为什么LayoutKind。顺序工作不同,如果一个结构体包含一个DateTime字段?
但是在我的例子中,即使子结构的属性改变了,布局也会改变,而数据类型没有任何改变。所以这看起来不像是优化。除此之外,我还想指出,另一个问题还没有得到解答。
在另一个问题中,他们提到在使用编组时尊重布局。我自己没有测试过,但我想知道为什么布局不受不安全代码的尊重,因为所有相关属性似乎都在适当的地方?文档是否在某个地方提到,除非完成封送处理,否则这些属性将被忽略?为什么?
考虑到这一点,我甚至可以期望LayoutKind。明确地工作可靠的不安全代码?
此外,文档还提到了保持结构具有预期布局的动机:

为了减少与Auto值相关的布局问题,c#、Visual Basic和c++编译器为值类型指定了顺序布局。


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

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

来自MSDN库文章中的LayoutKind枚举:

对象的每个成员在非托管内存中的精确位置由StructLayoutAttribute的设置显式控制。包装领域。每个成员必须使用FieldOffsetAttribute来指示该字段在类型中的位置。

相关短语突出显示,这不是在这个程序中发生的,指针仍然在很大程度上解引用托管内存。

是的,你所看到的与当一个结构体包含DateTime类型的成员时所发生的是相同的,该类型应用了[StructLayout(LayoutKind.Auto)]。CLR中决定布局的字段编组程序代码努力支持LayoutKind。对于托管结构也是顺序的。但如果它遇到任何与这一目标相冲突的成员国,它会毫不犹豫地迅速放弃。一个本身不是顺序的结构体就足够了。您可以在SSCLI20源代码src/clr/vm/fieldmarshalerer .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     */ 

结果是:

        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     */ 

用一个简单的4字节的内存保存。64位布局需要额外的填充,以确保在将其存储在数组中时仍然对齐。这些都是高度未记录的,并且可能会更改,请确保永远不要依赖托管内存布局。只有Marshal.StructureToPtr()可以给你一个保证。