gpt4 book ai didi

c# - LayoutKind.Sequential 当子结构具有 LayoutKind.Explicit 时未遵循

转载 作者:可可西里 更新时间:2023-11-01 08:10:06 26 4
gpt4 key购买 nike

运行这段代码时:

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

我在 x86 上得到的结果:
4

6
0
2
3
1
4
5
垃圾

我在 x64 上得到的是:
8

6
0
2
3
1
0
4
5

更多:
- 当我删除 LayoutKind.Explicit 和 FieldOffset 属性时,问题就消失了。
- 当我删除 Bool 字段时,问题就消失了。
- 当我删除 Long 字段时,问题就消失了。
- 请注意,在 x64 上似乎也忽略了 Pack=4 属性参数?

这适用于 .Net3.5 和 .Net4.0

我的问题:我错过了什么?或者这是一个错误?
我发现了一个类似的问题:
Why does LayoutKind.Sequential work differently if a struct contains a DateTime field?
但在我的例子中,即使子结构的属性发生变化,布局也会发生变化,而数据类型没有任何变化。所以它看起来不像是优化。除此之外,我想指出另一个问题仍然没有答案。
在另一个问题中,他们提到在使用编码时会遵守布局。我自己还没有测试过,但我想知道为什么不遵守不安全代码的布局,因为所有相关属性似乎都已到位?文档是否在某处提到除非完成编码,否则这些属性将被忽略?为什么?
考虑到这一点,我什至可以期望 LayoutKind.Explicit 能够可靠地处理不安全代码吗?
此外,文档提到了保持结构与预期布局的动机:

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


但是这个动机显然不适用于不安全的代码?

最佳答案

来自 LayoutKind 枚举的 MSDN 库文章:

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.

突出显示相关短语,此程序中没有发生这种情况,指针仍然在很大程度上取消引用托管内存。

是的,您所看到的与当结构包含 DateTime 类型的成员时发生的情况相同,该类型应用了 [StructLayout(LayoutKind.Auto)]。 CLR 中确定布局的字段编码程序代码也努力为托管结构提供 LayoutKind.Sequential。但如果遇到任何与此目标冲突的成员,它会迅速放弃而不会尖叫。本身不是顺序的结构就足够了。您可以在 SSCLI20 source 中看到这是完成的, src/clr/vm/fieldmarshaler.cpp, 搜索fDisqualifyFromManagedSequential

这将使它切换到自动布局,与应用于类的布局规则相同。它重新排列字段以最小化成员之间的填充。最终效果是所需的内存量较小。在“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() 可以给你保证。

关于c# - LayoutKind.Sequential 当子结构具有 LayoutKind.Explicit 时未遵循,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16333511/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com