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

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

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