使用 FieldOffset 意外行为的结构

Posted

技术标签:

【中文标题】使用 FieldOffset 意外行为的结构【英文标题】:structs using FieldOffset unexpected behaviour 【发布时间】:2011-07-04 00:57:24 【问题描述】:

我正在尝试理解显式结构布局和结构覆盖,但我没有看到我期望的行为。给定以下代码:

class Program


    static void Main(string[] args)
    
        byte[] bytes = new byte[17];
        bytes[0] = 0x01; // Age is 1    //IntField1
        bytes[1] = 0x00;                //IntField1
        bytes[2] = 0x00;                //IntField1
        bytes[3] = 0x00;                //IntField1
        bytes[4] = 0x02;                //IntField2
        bytes[5] = 0x00;                //IntField2
        bytes[6] = 0x00;                //IntField2
        bytes[7] = 0x00;                //IntField2

        bytes[8] = 0x41;                //CharArray A
        bytes[9] = 0x42;                //CharArray B
        bytes[10] = 0x43;               //CharArray C
        bytes[11] = 0x44;               //CharArray D

        bytes[12] = 0x45;               //CharArray E

        bytes[13] = 0x46;               //CharArray F
        bytes[14] = 0x00; // \0 decimal 0
        bytes[15] = 0x00; // \0 decimal 0
        bytes[16] = 0x01; // 1 decimal 1
        Console.WriteLine(Marshal.SizeOf(typeof(TestStruct)));

        TestStruct testStruct2 = (TestStruct) RawDeserialize(bytes, 0, typeof (TestStruct));

        Console.WriteLine(testStruct2);
        Console.ReadLine();
    
    public static object RawDeserialize( byte[] rawData, int position, Type anyType )
    
        int rawsize = Marshal.SizeOf( anyType );
        if( rawsize > rawData.Length )
            return null;

        IntPtr buffer = Marshal.AllocHGlobal( rawsize );
        Marshal.Copy( rawData, position, buffer, rawsize );
        object retobj = Marshal.PtrToStructure( buffer, anyType );
        Marshal.FreeHGlobal( buffer );
        return retobj;
    


[StructLayout(LayoutKind.Explicit, Pack = 1)]
public struct TestStruct

    [FieldOffset(0)]
    public int IntField1;
    [FieldOffset(4)]
    public int IntField2;
    [FieldOffset(8)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    public char[] CharArray;
    [FieldOffset(16)]
    public byte SomeByte;        

    [FieldOffset(8)]
    public TestStruct2 SubStruct;

    public override string ToString()
    
        return string.Format("IntField1: 0\nIntField2: 1\nCharArray: 2\nSomeByte: 3\nSubStruct:\n4", 
            IntField1, IntField2,  new string(CharArray), SomeByte, SubStruct);
    


[StructLayout(LayoutKind.Explicit, Pack = 1)]
public struct TestStruct2

    [FieldOffset(0)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    public char[] CharArray1;
    [FieldOffset(0)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public char[] CharArray2;
    [FieldOffset(4)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
    public char[] CharArray3;

    public override string ToString()
    
        return string.Format("CharArray1: 0\nCharArray2: 1\nCharArray3: 2",
           new string(CharArray1), new string(CharArray2), new string(CharArray3));
    

我希望这样的结果是这样的:

IntField1: 1 IntField2: 2 字符数组:ABCDEF SomeByte:1 子结构: CharArray1:ABCDEF CharArray2: ABCD CharArray3: E

但结果是:

IntField1: 1 IntField2: 2 字符数组:ABCD SomeByte:1 子结构: CharArray1: ABCD CharArray2: ABCD CharArray3: EF

为什么 TestStruct 中的 CharArray 的长度是 4?我预计它有 6 个字符 ABCDEF,但它只包含 ABCD。 TestStruct2.CharArray1 也一样。

【问题讨论】:

我发现这篇文章很有帮助:m.developerfusion.com/article/84519/mastering-structs-in-c 谢谢。我实际上读过它,但仍然不明白我所看到的。也许我需要再仔细阅读一下...... 【参考方案1】:

通过将 TestStruct2 放在 CharArray 之后但在相同的偏移量处,现在指向 TestStruct2 的 CharArray2 的指针正在覆盖过去指向 TestStruct 自己的 chararray 的指针。

如果您注释掉或更改 TestStruct2 的 CharArray2 的长度,您将看到预期的结果。

同样观察当你把 struct2 放在首位时会发生什么,即:

[StructLayout(LayoutKind.Explicit, Pack = 1)]
    public struct TestStruct
    
        [FieldOffset(8)]
        public TestStruct2 SubStruct;

        [FieldOffset(0)]
        public int IntField1;
        [FieldOffset(4)]
        public int IntField2;
        [FieldOffset(8)]
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
        public char[] CharArray;
        [FieldOffset(16)]
        public byte SomeByte;        


        public override string ToString()
        
            return string.Format("IntField1: 0\nIntField2: 1\nCharArray: 2\nSomeByte: 3\nSubStruct:\n4", 
                IntField1, IntField2,  new string(CharArray), SomeByte, SubStruct);
        
    

现在效果反转了,TestStruct2 的 CharArray2 有六个字符长。

【讨论】:

哇非常有趣!最好了解发生这种情况的原因。不过谢谢你。 因为内存是重叠的:你试图让相同的内存位置(偏移 8:11)指向两个(实际上是三个)不同的东西,一个大小为 4 的数组和 [two]大小为 6 的数组[s]。如果您更改 CharArray2 的长度,您会看到其他的也发生变化。【参考方案2】:

要记住的一点是,C# 中的 char 是 2 个字节的 unicode 字符。

虽然在保留子结构的同时我仍然无法获得预期的结果。重叠的 SizeConst 数组似乎把事情搞砸了。

【讨论】:

【参考方案3】:

char[] 是一个引用类型,它的大小是一个 IntPtr,可以是 4 或 8 字节 - 取决于平台(x86 或 x64),并且它的值不会存储在结构中。

MarshalAs 属性不会改变信息在结构中的存储方式,只会改变信息的转换方式(例如,与非托管代码之间的转换方式)。

【讨论】:

以上是关于使用 FieldOffset 意外行为的结构的主要内容,如果未能解决你的问题,请参考以下文章

fieldoffset 的未定义行为[重复]

x64 上的文件时间

如何在没有副本的情况下将结构转换为字节数组?

String s1 == String s2 (true) 但 FieldOffset 不同

显式结构布局上的未分配字段

指针的意外行为(操作时)但使用双指针时定义的行为