代码之家  ›  专栏  ›  技术社区  ›  Matthew Watson

如果结构包含DateTime字段,为什么LayoutKind.Sequential的工作方式不同?

  •  20
  • Matthew Watson  · 技术社区  · 14 年前

    请考虑以下代码(必须在启用“不安全”的情况下编译的控制台应用程序):

    using System;
    using System.Runtime.InteropServices;
    
    namespace ConsoleApplication3
    {
        static class Program
        {
            static void Main()
            {
                Inner test = new Inner();
    
                unsafe
                {
                    Console.WriteLine("Address of struct   = " + ((int)&test).ToString("X"));
                    Console.WriteLine("Address of First    = " + ((int)&test.First).ToString("X"));
                    Console.WriteLine("Address of NotFirst = " + ((int)&test.NotFirst).ToString("X"));
                }
            }
        }
    
        [StructLayout(LayoutKind.Sequential)]
        public struct Inner
        {
            public byte First;
            public double NotFirst;
            public DateTime WTF;
        }
    }
    

    现在,如果我运行上面的代码,我将得到类似于以下内容的输出:

    结构地址=40f2c
    第一个地址=40F2D4
    地址NotFirst=40f2c

    注意,First的地址与struct的地址不同;但是,NotFirst的地址

    现在注释掉结构中的“DateTime WTF”字段,然后再次运行它。 这次,我得到的输出类似于:

    结构地址=15F2E0
    第一个地址=15F2E0
    地址NotFirst=15F2E8

    现在“第一” 具有与结构相同的地址。

    [编辑]注意:我已经验证了当您使用Marshal.StructureToPtr()来封送结构时,数据

    [EDIT3]此问题的原始来源来自MSDN C#论坛:

    http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/fb84bf1d-d9b3-4e91-823e-988257504b30

    6 回复  |  直到 14 年前
        1
  •  16
  •   Community CDub    7 年前

    如果结构包含DateTime字段,为什么LayoutKind.Sequential的工作方式不同?

    它与 (surprising) fact that DateTime itself has layout "Auto" (link to SO question by myself)

    static class Program
    {
        static unsafe void Main()
        {
            Console.WriteLine("64-bit: {0}", Environment.Is64BitProcess);
            Console.WriteLine("Layout of OneField: {0}", typeof(OneField).StructLayoutAttribute.Value);
            Console.WriteLine("Layout of Composite: {0}", typeof(Composite).StructLayoutAttribute.Value);
            Console.WriteLine("Size of Composite: {0}", sizeof(Composite));
            var local = default(Composite);
            Console.WriteLine("L: {0:X}", (long)(&(local.L)));
            Console.WriteLine("M: {0:X}", (long)(&(local.M)));
            Console.WriteLine("N: {0:X}", (long)(&(local.N)));
        }
    }
    
    [StructLayout(LayoutKind.Auto)]  // also try removing this attribute
    struct OneField
    {
        public long X;
    }
    
    struct Composite   // has layout Sequential
    {
        public byte L;
        public double M;
        public OneField N;
    }
    

    样本输出:

    64-bit: True
    Layout of OneField: Auto
    Layout of Composite: Sequential
    Size of Composite: 24
    L: 48F050
    M: 48F048
    N: 48F058
    

    如果我们从 OneField ,一切如期而至。例子:

    64-bit: True
    Layout of OneField: Sequential
    Layout of Composite: Sequential
    Size of Composite: 24
    L: 48F048
    M: 48F050
    N: 48F058
    

    这些例子与 平台编译(因此大小为24,是8的3倍,这并不奇怪),但对于x86,我们看到的是相同的“无序”指针地址。

    所以我想我可以断定 一个字段 日期时间 在您的示例中)对包含 成员,即使该复合结构本身具有布局 Sequential . 我不确定这是否有问题(甚至是必要的)。


    它不再试图保持顺序 当其中一个成员是 Auto

        2
  •  6
  •   Jonathan S. Shapiro    10 年前

    仔细阅读布局规则的规范。 仅当对象在非托管内存中公开时,布局规则才控制布局 . 这意味着编译器可以随意放置字段,直到实际导出对象为止。令我有些惊讶的是,这甚至是固定布局!

    说明此处选择的最终布局,但这与编译器忽略布局规范的原因无关。

    包含

    在那里!不是很明显!

    这里推荐固定布局的各种注释通常是很好的建议,但在这种情况下,这并不一定有任何效果,因为包含DateTime字段可以免除编译器执行布局的责任。更糟的是:编译器不是 必修的 尊重布局,但它是 尊重布局。这意味着CLR的后续版本在这方面可以自由地有不同的行为。

    在我看来,对布局的处理是CLI中的一个设计缺陷。当用户指定一个布局时,编译器不应该绕着它们走。最好把事情简单化,让编译器按要求去做。尤其是在布局方面。”我们都知道,“聪明”是一个四个字母的单词。

        3
  •  3
  •   Matthew Watson    14 年前

    问:“在使用COM DATETIME类型的C/C++结构互操作时,这种行为有什么影响吗?”

    答:不,因为在使用封送处理时会考虑布局。(我根据经验证实了这一点。)

    问题“有人能解释一下吗?”.

    答:我仍然不确定这一点,但是由于没有定义结构的内部表示,编译器可以做它喜欢做的事情。

        4
  •  2
  •   Ian Ringrose    14 年前

    几个因素

    • CPU缓存可能会更好地工作,如果没有洞在打击

    所以C编译器有一些未经文档化的规则,用来尝试获取 最好的 如果您需要知道结构的布局,那么您应该自己指定它,而不是让编译器来决定。

    但是LayoutKind.Sequential确实会阻止编译器更改字段的顺序。

        5
  •  2
  •   Lee Louviere    13 年前

    它正确地封送到本机结构中的原因是,数据使用封送值设置的属性复制到本机内存中。

    因此,管理结构的安排对原生结构的安排没有影响。只有属性才影响原生结构的排列。

        6
  •  1
  •   erict    14 年前

    如果你要用C/C++来互操作,我会一直使用StuttDebug。我将使用Explicit而不是Sequential,并使用FieldOffset指定每个位置。另外,添加Pack变量。

    [StructLayout(LayoutKind.Explicit, Pack=1, CharSet=CharSet.Unicode)]
    public struct Inner
    {
        [FieldOffset(0)]
        public byte First;
        [FieldOffset(1)]
        public double NotFirst;
        [FieldOffset(9)]
        public DateTime WTF;
    }
    

    包变量在C++代码中尤其重要,它可以编译在具有不同字大小的不同系统上。

    我也会忽略使用不安全代码时可以看到的地址。只要封送处理正确,编译器做什么并不重要。