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