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 байтов), или поле "Пакет" имеет значение 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-байтовой границе, которая меньше 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 и 32 байта в .NET 5+. Два байта занимают первые два байта памяти, а затем два байта заполнения, а затем целое число. Далее — однобайтовый массив, за которым следует три байта заполнения. Так как десятичное значение состоит из нескольких полей, выравнивание основано на самом большом из его полей, а не на размере 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 часто используется при экспорте структур во время операций записи диска и сети. Поле также часто используется во время вызовов и операций взаимодействия платформы.

Иногда поле используется для уменьшения требований к памяти путем создания более жесткого размера упаковки. Однако это использование требует тщательного рассмотрения фактических ограничений оборудования и может на самом деле снизить производительность.

Применяется к