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

C# 如果结构包含DateTime字段,为什么LayoutKind.Sequential的工作方式不同?,c#,datetime,marshalling,structlayout,C#,Datetime,Marshalling,Structlayout,如果结构包含DateTime字段,为什么LayoutKind.Sequential的工作方式不同 考虑以下代码(必须在启用“不安全”的情况下编译的控制台应用程序): 现在,如果我运行上面的代码,我会得到如下类似的输出: 结构的地址=40f2c 第一个地址=40F2D4 NotFirst的地址=40f2c 请注意,First的地址与struct的地址不同;但是,NotFirst的地址与结构的地址相同 现在注释掉结构中的“DateTime WTF”字段,然后再次运行它。 这一次,我得到了如下类似的输

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

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

现在,如果我运行上面的代码,我会得到如下类似的输出:

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

请注意,First的地址与struct的地址不同;但是,NotFirst的地址与结构的地址相同

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

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

现在,“First”与结构具有相同的地址

考虑到LayoutKind.Sequential的使用,我发现这种行为令人惊讶。有人能解释一下吗?当与使用Com DATETIME类型的C/C++结构进行互操作时,这种行为是否有任何影响

[编辑]注意:我已经验证,当您使用Marshal.StructureToPtr()封送结构时,数据是按照正确的顺序封送的,并且“First”字段是第一个字段。这似乎表明它可以很好地与互操作配合使用。神秘之处在于为什么内部布局会发生变化——当然,内部布局从未指定,因此编译器可以随心所欲

[EDIT2]从结构声明中删除了“不安全的”(这是我在做一些测试时留下的)

[EDIT3]这个问题的原始来源是MSDN C#论坛:

几个因素

  • 双打如果对齐,速度会快得多
  • 如果内存中没有“洞”,CPU缓存可能工作得更好
因此C#编译器有一些未记录的规则,用于尝试获得结构的“最佳”布局,这些规则可能会考虑结构的总大小,和/或如果它包含另一个结构等。如果你需要知道结构的布局,那么你应该自己指定,而不是让编译器决定。


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

回答我自己的问题(建议):

问题:“当与使用Com DATETIME类型的C/C++结构进行互操作时,这种行为是否会产生任何影响?”

答:没有,因为使用编组时会考虑布局。(我以经验验证了这一点。)

问题“有人能提供解释吗?”


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

如果你想与C/C++进行互操作,我将始终对StructLayout进行具体说明。我将使用Explicit代替顺序,并使用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;
}
听起来DateTime无论如何都无法封送,只能封送到字符串(bingle封送DateTime)

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


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

您正在检查托管结构中的地址。封送处理属性不能保证托管结构中字段的排列

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

因此,管理结构的安排对本地结构的安排没有影响。只有属性影响本机结构的排列

如果使用封送属性设置的字段以与本机数据相同的方式存储在托管数据中,那么marshal.StructureToPtr中就没有意义了,您只需通过字节复制数据即可

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

这是与环境有关的。此代码复制您看到的行为:

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 这些示例都是针对x64平台编译的(因此大小为24,三倍于8并不奇怪),但对于x86,我们也看到了相同的“无序”指针地址

因此,我想我可以得出结论,
OneField
(在您的示例中是
DateTime
)的布局对包含
OneField
成员的结构的布局有影响,即使该复合结构本身具有布局
顺序
。我不确定这是否有问题(甚至是必需的)



根据Hans Passant在另一个线程中的评论,当其中一个成员是
Auto
layout结构时,它不再试图保持它的顺序。

更仔细地阅读布局规则规范布局规则仅在对象在非托管内存中公开时控制布局。这意味着编译器可以随意放置字段,直到对象实际导出为止。让我有点惊讶的是,这甚至适用于FixedLayout

Ian Ringrose在编译器效率问题上的观点是正确的,这确实解释了此处选择的最终布局,但这与编译器忽略布局规范的原因无关

一些人指出DateTime具有自动布局功能。这是你惊喜的最终来源,但原因有点模糊。Auto layout的文档中说,“使用[Auto]layout定义的对象不能暴露在托管代码之外。尝试这样做会产生异常。”还请注意DateTime 64-bit: True Layout of OneField: Auto Layout of Composite: Sequential Size of Composite: 24 L: 48F050 M: 48F048 N: 48F058 64-bit: True Layout of OneField: Sequential Layout of Composite: Sequential Size of Composite: 24 L: 48F048 M: 48F050 N: 48F058