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, Byte поля выравниваются по 1-байтовой границе, Int16 поля — по 2-байтовой границе, а Int32 поля — по 4-байтовой границе.
  • Для соответствия требованиям к выравниванию между полями добавляется заполнение.

Например, рассмотрим следующую структуру, состоящую из двух Byte полей и одного Int32 поля, если она используется с различными Pack значениями поля.

using System;

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

Важно!

Для успешной компиляции примеров 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-байтовой границы, заданной полем Pack.

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 часто используется при экспорте структур во время операций записи на диск и в сеть. Поле также часто используется во время вызовов платформы и операций взаимодействия.

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

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