StructLayoutAttribute.Pack 字段

定义

控制内存中类或结构的数据字段的对齐方式。

public: int Pack;
public int Pack;
val mutable Pack : int
Public Pack As Integer 

字段值

注解

Pack 字段控制类型字段在内存中的对齐方式。 它会影响属性 LayoutKind.Sequential 。 该值指示当前平台的默认打包大小。 该值 Pack 必须为 0、1、2、4、8、16、32、64 或 128。 默认值为 0。

类型实例的字段使用以下规则进行对齐:

  • 类型的对齐方式是其最大元素(例如 1、2、4 或 8 字节)或指定包装大小(以较小者为准)。
  • 每个字段必须与其自己的大小或类型的对齐方式保持一致,以较小者为准。 由于该类型的默认对齐方式是其最大元素的大小(大于或等于所有其他字段长度),因此这通常意味着字段按其大小对齐。 例如,即使类型中最大的字段是 64 位 (8 字节) 整数或 Pack 字段设置为 8,字段在 1 字节边界上对齐, ByteInt16 字段在 2 字节边界上对齐,字段 Int32 在 4 字节边界上对齐。
  • 在字段之间添加填充以满足对齐要求。

例如,考虑以下结构,该结构由两 Byte 个字段和一个 Int32 字段组成,当它与字段的各种值 Pack 一起使用时。

using System;

struct ExampleStruct
{
    public byte b1;
    public byte b2;
    public int i3;
}

Important

若要成功编译 C# 示例,必须指定 /unsafe 编译器开关。

如果指定默认打包大小,则结构的大小为 8 字节。 这两个字节占用前两个字节的内存,因为字节必须在一字节边界上对齐。 由于类型的默认对齐方式为 4 个字节,这是其最大字段的大小, i3因此有两个字节的填充后跟整数字段。

using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential, Pack = 0)]
struct ExampleStruct1
{
    public byte b1;
    public byte b2;
    public int i3;
}

public class Example1
{
    public unsafe static void Main()
    {
        ExampleStruct1 ex = new();
        byte* addr = (byte*)&ex;
        Console.WriteLine("Size:      {0}", sizeof(ExampleStruct1));
        Console.WriteLine("b1 Offset: {0}", &ex.b1 - addr);
        Console.WriteLine("b2 Offset: {0}", &ex.b2 - addr);
        Console.WriteLine("i3 Offset: {0}", (byte*)&ex.i3 - addr);
    }
}
// The example displays the following output:
//       Size:      8
//       b1 Offset: 0
//       b2 Offset: 1
//       i3 Offset: 4

如果 Pack 设置为 2,则结构的大小为 6 字节。 与之前一样,这两个字节占用前两个字节的内存。 由于字段现在在 2 字节边界上对齐,因此第二个字节和整数之间没有填充。

using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential, Pack = 2)]
struct ExampleStruct2
{
    public byte b1;
    public byte b2;
    public int i3;
}

public class Example2
{
    public unsafe static void Main()
    {
        ExampleStruct2 ex = new();
        byte* addr = (byte*)&ex;
        Console.WriteLine("Size:      {0}", sizeof(ExampleStruct2));
        Console.WriteLine("b1 Offset: {0}", &ex.b1 - addr);
        Console.WriteLine("b2 Offset: {0}", &ex.b2 - addr);
        Console.WriteLine("i3 Offset: {0}", (byte*)&ex.i3 - addr);
    }
}
// The example displays the following output:
//       Size:      6
//       b1 Offset: 0
//       b2 Offset: 1
//       i3 Offset: 2

如果 Pack 设置为 4,则结构的大小与默认情况相同,其中类型对齐由其最大字段的大小定义, i3即 4。

using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct ExampleStruct3
{
    public byte b1;
    public byte b2;
    public int i3;
}

public class Example3
{
    public unsafe static void Main()
    {
        ExampleStruct3 ex = new();
        byte* addr = (byte*)&ex;
        Console.WriteLine("Size:      {0}", sizeof(ExampleStruct3));
        Console.WriteLine("b1 Offset: {0}", &ex.b1 - addr);
        Console.WriteLine("b2 Offset: {0}", &ex.b2 - addr);
        Console.WriteLine("i3 Offset: {0}", (byte*)&ex.i3 - addr);
    }
}
// The example displays the following output:
//       Size:      8
//       b1 Offset: 0
//       b2 Offset: 1
//       i3 Offset: 4

如果 Pack 设置为 8,则结构的大小仍与默认情况相同,因为 i3 字段在 4 字节边界上对齐,这小于 Pack 字段指定的 8 字节边界。

using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential, Pack = 8)]
struct ExampleStruct4
{
    public byte b1;
    public byte b2;
    public int i3;
}

public class Example4
{
    public unsafe static void Main()
    {
        ExampleStruct4 ex = new();
        byte* addr = (byte*)&ex;
        Console.WriteLine("Size:      {0}", sizeof(ExampleStruct4));
        Console.WriteLine("b1 Offset: {0}", &ex.b1 - addr);
        Console.WriteLine("b2 Offset: {0}", &ex.b2 - addr);
        Console.WriteLine("i3 Offset: {0}", (byte*)&ex.i3 - addr);
    }
}
// The example displays the following output:
//       Size:      8
//       b1 Offset: 0
//       b2 Offset: 1
//       i3 Offset: 4

若要采用另一个示例,请考虑以下结构,其中包括两个字节字段、一个 32 位有符号整数字段、一个单元素字节数组和一个十进制值。 使用默认打包大小时,结构的大小为 28 字节,.NET Framework,.NET 5+ 为 32 字节。 这两个字节占用前两个字节的内存,后跟两个字节的填充,后跟整数。 接下来是一字节数组,后跟三个字节的填充。 由于小数值由多个字段组成,因此对齐方式基于其字段的最大大小,而不是整个结构的大小 Decimal 。 在 .NET 5 及更高版本中,Decimal 结构由两个 Int32 字段和一个 8 字节字段组成,因此 Decimal 字段 d5 在 8 字节边界上对齐。 在 .NET Framework 中,Decimal 结构由四个 Int32 字段组成,因此 Decimal 字段 d5 在 4 字节边界上对齐。

using System;

unsafe struct ExampleStruct5
{

    public byte b1;
    public byte b2;
    public int i3;
    public fixed byte a4[1];
    public decimal d5;
}

public class Example5
{
    public unsafe static void Main()
    {
        ExampleStruct5 ex = new();
        byte* addr = (byte*)&ex;
        Console.WriteLine("Size:      {0}", sizeof(ExampleStruct5));
        Console.WriteLine("b1 Offset: {0}", &ex.b1 - addr);
        Console.WriteLine("b2 Offset: {0}", &ex.b2 - addr);
        Console.WriteLine("i3 Offset: {0}", (byte*)&ex.i3 - addr);
        Console.WriteLine("a4 Offset: {0}", ex.a4 - addr);
        Console.WriteLine("d5 Offset: {0}", (byte*)&ex.d5 - addr);
    }
}
// The example displays the following output:
//
// .NET 5+:
//       Size:      32
//       b1 Offset: 0
//       b2 Offset: 1
//       i3 Offset: 4
//       a4 Offset: 8
//       d5 Offset: 16
//
// .NET Framework:
//       Size:      28
//       b1 Offset: 0
//       b2 Offset: 1
//       i3 Offset: 4
//       a4 Offset: 8
//       d5 Offset: 12

如果 Pack 设置为 2,则结构的大小为 24 字节。 与默认对齐相比,删除了两个字节和整数之间的两个字节填充,因为该类型的对齐方式现在为 4 而不是 2。 之后的三个字节填充 a4 已被一个字节填充替换,因为 d5 现在在 2 字节边界而不是 4 字节边界上对齐。

using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential, Pack = 2)]
unsafe struct ExampleStruct6
{

    public byte b1;
    public byte b2;
    public int i3;
    public fixed byte a4[1];
    public decimal d5;
}

public class Example6
{
    public unsafe static void Main()
    {
        ExampleStruct6 ex = new();
        byte* addr = (byte*)&ex;
        Console.WriteLine("Size:      {0}", sizeof(ExampleStruct6));
        Console.WriteLine("b1 Offset: {0}", &ex.b1 - addr);
        Console.WriteLine("b2 Offset: {0}", &ex.b2 - addr);
        Console.WriteLine("i3 Offset: {0}", (byte*)&ex.i3 - addr);
        Console.WriteLine("a4 Offset: {0}", ex.a4 - addr);
        Console.WriteLine("d5 Offset: {0}", (byte*)&ex.d5 - addr);
    }
}
// The example displays the following output:
//       Size:      24
//       b1 Offset: 0
//       b2 Offset: 1
//       i3 Offset: 2
//       a4 Offset: 6
//       d5 Offset: 8

如果 Pack 设置为 16,则结构的大小与默认情况相同,因为此结构中的所有对齐要求都小于 16。

using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential, Pack = 16)]
unsafe struct ExampleStruct7
{

    public byte b1;
    public byte b2;
    public int i3;
    public fixed byte a4[1];
    public decimal d5;
}

public class Example7
{
    public unsafe static void Main()
    {
        ExampleStruct7 ex = new();
        byte* addr = (byte*)&ex;
        Console.WriteLine("Size:      {0}", sizeof(ExampleStruct7));
        Console.WriteLine("b1 Offset: {0}", &ex.b1 - addr);
        Console.WriteLine("b2 Offset: {0}", &ex.b2 - addr);
        Console.WriteLine("i3 Offset: {0}", (byte*)&ex.i3 - addr);
        Console.WriteLine("a4 Offset: {0}", ex.a4 - addr);
        Console.WriteLine("d5 Offset: {0}", (byte*)&ex.d5 - addr);
    }
}
// The example displays the following output:
//
// .NET 5+:
//       Size:      32
//       b1 Offset: 0
//       b2 Offset: 1
//       i3 Offset: 4
//       a4 Offset: 8
//       d5 Offset: 16
//
// .NET Framework:
//       Size:      28
//       b1 Offset: 0
//       b2 Offset: 1
//       i3 Offset: 4
//       a4 Offset: 8
//       d5 Offset: 12

Pack 在磁盘和网络写入操作期间导出结构时,经常使用此字段。 该字段在平台调用和互操作操作期间也经常使用。

有时,该字段用于通过生成更严格的包装大小来降低内存需求。 但是,此用法需要仔细考虑实际的硬件约束,并且实际上可能会降低性能。

适用于