【问题标题】:LayoutKind.Sequential not followed when substruct has LayoutKind.Explicit子结构具有 LayoutKind.Explicit 时不遵循 LayoutKind.Sequential
【发布时间】:2013-04-26 08:35:14
【问题描述】:

运行此代码时:

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

我的问题:我错过了什么?还是这是一个错误?
我发现了一个类似的问题:
Why does LayoutKind.Sequential work differently if a struct contains a DateTime field?
但在我的情况下,即使子结构的属性发生变化,布局也会发生变化,而数据类型没有任何变化。所以它看起来不像是优化。除此之外,我想指出,另一个问题仍未得到解答。
在另一个问题中,他们提到使用编组时尊重布局。我自己没有测试过,但我想知道为什么布局不尊重不安全代码,因为所有相关属性似乎都到位?文档是否在某处提到除非完成编组,否则这些属性将被忽略?为什么?
考虑到这一点,我什至可以期望 LayoutKind.Explicit 能够可靠地处理不安全的代码吗?
此外,文档还提到了将结构保持在预期布局中的动机:

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


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

【问题讨论】:

    标签: c# .net unsafe structlayout layoutkind.explicit


    【解决方案1】:

    来自 MSDN Library 中有关 LayoutKind 枚举的文章:

    对象的每个成员在非托管内存中的精确位置是明确控制的,取决于 StructLayoutAttribute.Pack 字段的设置。每个成员都必须使用 FieldOffsetAttribute 来指示该字段在类型中的位置。

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

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

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

    所以不是用 /* 偏移量 - 大小 */ 注释的显式布局:

            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 位布局需要额外的填充,以确保 long 在存储在数组中时仍然对齐。这都是高度未记录的并且可能会更改,请确保永远不要依赖托管内存布局。只有 Marshal.StructureToPtr() 可以给你一个保证。

    【讨论】:

      猜你喜欢
      • 2019-08-14
      • 2021-08-26
      • 2014-06-26
      • 2021-07-07
      • 1970-01-01
      • 1970-01-01
      • 2017-12-14
      • 2013-01-20
      • 1970-01-01
      相关资源
      最近更新 更多