C# 当子结构具有LayoutKind.Explicit时,不遵循Sequential
运行此代码时:C# 当子结构具有LayoutKind.Explicit时,不遵循Sequential,c#,.net,unsafe,structlayout,layoutkind.explicit,C#,.net,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.
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)
二,
三,
一,
6
0
四,
五,
垃圾 我在x86上得到了什么:
四,
6
0
二,
三,
一,
四,
五,
垃圾 我在x64上得到的是什么:
八,
6
0
二,
三,
一,
0
四,
五, 更多信息:
-当我删除LayoutKind.Explicit和FieldOffset属性时,问题就消失了。
-当我移除布尔字段时,问题就消失了。
-当我删除长字段时,问题就消失了。
-请注意,在x64上,似乎也忽略了Pack=4属性参数 这适用于.Net3.5和.Net4.0 我的问题:我错过了什么?还是这是一个bug?
我发现了一个类似的问题:
但在我的例子中,即使子结构的属性改变,布局也会改变,而数据类型没有任何改变。因此,它看起来不像是一个优化。此外,我想指出,另一个问题仍然没有答案。
在另一个问题中,他们提到在使用编组时要尊重布局。我自己还没有测试过,但我想知道,既然所有相关属性似乎都已就位,为什么布局不受不安全代码的尊重?文档中是否提到,除非完成编组,否则这些属性将被忽略?为什么?
考虑到这一点,我甚至可以期望LayoutKind.Explicit为不安全代码可靠地工作吗?
此外,文档还提到了保持结构具有预期布局的动机: <> >为了减少与自动值相关的布局相关问题,C++、VisualBasic和C++编译器指定值类型的顺序布局。
但这一动机显然不适用于不安全代码?来自MSDN库关于LayoutKind枚举的文章: 根据StructLayoutAttribute.Pack字段的设置,明确控制对象的每个成员在非托管内存中的精确位置。每个成员都必须使用FieldOffsetAttribute来指示该字段在类型中的位置 相关短语突出显示,这并没有发生在这个程序中,指针仍然在很大程度上解除对托管内存的引用 是的,您看到的情况与结构包含DateTime类型的成员时发生的情况相同,DateTime类型应用了[StructLayout(LayoutKind.Auto)]。CLR中用于确定布局的字段封送拆收器代码也致力于遵守托管结构的LayoutKind.Sequential。但是,如果它遇到任何与这一目标相冲突的成员,它将很快毫无怨言地放弃。一个本身不连续的结构就足够了。您可以在src/clr/vm/fieldmarshaler.cpp中看到这一点,搜索
fdisqualifyfromsManagedSequential
这将使它切换到自动布局,与应用于类的布局规则相同。它重新排列字段以最小化成员之间的填充。其净效果是所需的内存量更小。“Bool”成员后有7个字节的填充,未使用的空间用于将“Long”成员与8的倍数地址对齐。当然,这非常浪费,它通过将long作为布局中的第一个成员来修复这个问题
因此,与带/*offset-size*/注释的显式布局不同:
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()可以为您提供保证