LayoutKind.Sequential不遵守时substruct有LayoutKind.ExplicitSequential、LayoutKind、Explicit、substruct

2023-09-03 00:46:09 作者:持刀稳情场

在运行此code:

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 的垃圾的

I expect this output BOTH on x86 and x64: 4 or 8 (depending on x86 or x64) 2 3 1 6 0 4 5 garbage

我所得到的,而不是在x86: 4 6 0 2 3 1 4 5 的垃圾的

What I get instead on x86: 4 6 0 2 3 1 4 5 garbage

我所得到的,而不是在x64: 8 6 0 2 3 1 0 4 5

What I get instead on x64: 8 6 0 2 3 1 0 4 5

更多: - 的问题消失了,当我删除LayoutKind.Explicit和FieldOffset属性。 - 的问题消失了,当我删除了布尔领域。 - 的问题消失了,当我删除了龙场。 - 请注意,在x64似乎包= 4属性参数被忽略太

More: - The problem goes away when I remove the LayoutKind.Explicit and FieldOffset attributes. - The problem goes away when I remove the Bool field. - The problem goes away when I remove the Long field. - Note that on x64 it seems that the Pack=4 attribute parameter is ignored too?

这适用于.NET3.5也.Net4.0

This applies in .Net3.5 and also .Net4.0

我的问题:我缺少什么?或者这是一个错误? 我发现了一个类似的问题: Why确实LayoutKind.Sequential工作不同,如果一个结构包含一个日期时间字段? 但是,在我的情况下,布局更改的substruct变化,即使该属性,无需更改数据类型。所以它看起来并不像一个优化。除此之外,我想指出的是,其他的问题仍然没有答案。 酒店在其他的疑问,他们提到,布局使用编组时尊重。我还没有测试过自己,但我不知道为什么布局不尊重不安全code,因为所有相关属性似乎是在地方吗?请问文件提及的地方,这些属性被忽略,除非编组站做?为什么? 考虑到这一点,可我甚至希望LayoutKind.Explicit可靠地工作不安全code? 此外,文件中提到保持结构与预计的布局的动机:

My question: what am I missing? Or is this a bug? I found a similar question: Why does LayoutKind.Sequential work differently if a struct contains a DateTime field? But in my case the layout changes even when the attribute of the substruct changes, without any changes to data types. So it does not look like an optimization. Besides that, I would like to point out that the other question is still unanswered. In that other question they mention that the layout is respected when using Marshalling. I havent tested that myself but I wonder why is the layout not respected for unsafe code, since all the relevant attributes seem to be in place? Does the documentation mention somewhere that these attributes are ignored unless Marshalling is done? Why? Considering this, can I even expect LayoutKind.Explicit to work reliably for unsafe code? Moreover, the documentation mentions the motive of keeping structs with expected layout:

要减少与自动值,C#,Visual Basic中相关联的布局有关的问题,和C ++编译器的值类型指定顺序布局。

To reduce layout-related problems associated with the Auto value, C#, Visual Basic, and C++ compilers specify Sequential layout for value types.

但这种动机显然并不适用于不安全code?

But this motive apparently does not apply to unsafe code?

推荐答案

从为LayoutKind枚举在MSDN Library文章:

From the MSDN Library article for LayoutKind enumeration:

对象的在非托管内存中的每个成员的precise位置明确控制,受StructLayoutAttribute.Pack字段的设置。每个成员都必须使用FieldOffsetAttribute以表明该领域内的类型的位置。

The precise position of each member of an object in unmanaged memory is explicitly controlled, subject to the setting of the StructLayoutAttribute.Pack field. Each member must use the FieldOffsetAttribute to indicate the position of that field within the type.

相关词组强调,这不是在这个程序中,指针仍然是非常非关联化管理的内存。

Relevant phrase highlighted, this is not happening in this program, the pointer is still very much dereferencing managed memory.

是的,你所看到的是相同的,当一个结构包含DateTime类型,具有类型的成员会发生什么[StructLayout(LayoutKind.Auto)应用。本场编组code在CLR中,确定布局使得努力兑现LayoutKind.Sequential的管理结构为好。但它会很快放弃没有尖叫声,如果遇到与这一目标相冲突的任何成员。这本身就是非顺序一个结构是足够的。你可以看到这个正在做的 SSCLI20源,SRC / CLR / VM /fieldmarshaler.cpp,搜索 fDisqualifyFromManagedSequential

And yes, what you are seeing is identical to what happens when a struct contains a member of type DateTime, a type that has [StructLayout(LayoutKind.Auto)] applied. The field marshaller code in the CLR that determines layout makes an effort to honor LayoutKind.Sequential for managed structs as well. But it will quickly give up without a squeal if it encounters any member that conflicts with this goal. A struct that itself is not sequential is sufficient for that. You can see this being done in the SSCLI20 source, src/clr/vm/fieldmarshaler.cpp, search for fDisqualifyFromManagedSequential

这将使其切换到自动布局,这是应用于类相同的布局规则。它重新排列领域,以尽量减少成员间的填充。同的净效果是所需的内存量较小。有7个字节的填充的布尔部件后,未使用的空间,以得到长构件对准的地址这8.非常浪费当然的倍数,它解决了通过使长的第一构件在布局

Which will make it switch to automatic layout, the same layout rule that's applied to classes. It rearranges fields to minimize the padding between members. With the net effect that the amount of memory required is smaller. There are 7 bytes of padding after the "Bool" member, unused space to get the "Long" member aligned to an address that's a multiple of 8. Very wasteful of course, it fixes that by making the long the first member in the layout.

因此​​,不是用/ *抵消了明确的布局 - 尺寸* /注释:

So instead of the explicit layout with /* offset - size */ annotated:

        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位布局需要额外的填充,以确保当它被存储在一个阵列的长仍然对准。这都是非常无证,如有更改,一定要永远承担管理内存布局的依赖。只有Marshal.StructureToPtr()可以给你保证。

With an easy 4 bytes of memory saved. The 64-bit layout requires additional padding to ensure that the long is still aligned when it is stored in an array. This is all highly undocumented and subject to change, be sure to never take a dependency on managed memory layout. Only Marshal.StructureToPtr() can give you a guarantee.

相关推荐
 
精彩推荐
图片推荐