Rediger

Del via


StructLayoutAttribute.Pack Field

Definition

Controls the alignment of data fields of a class or structure in memory.

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

Field Value

Remarks

The Pack field controls the alignment of a type's fields in memory. It affects the LayoutKind.Sequential property. The value indicates the default packing size for the current platform. The value of Pack must be 0, 1, 2, 4, 8, 16, 32, 64, or 128. The default value is 0.

The fields of a type instance are aligned by using the following rules:

  • The alignment of a type is the size of its largest element (for example, 1, 2, 4, or 8 bytes) or the specified packing size, whichever is smaller.
  • Each field must align with fields of its own size or the alignment of the type, whichever is smaller. Because the default alignment of the type is the size of its largest element, which is greater than or equal to all other field lengths, this usually means that fields are aligned by their size. For example, even if the largest field in a type is a 64-bit (8-byte) integer or the Pack field is set to 8, Byte fields align on 1-byte boundaries, Int16 fields align on 2-byte boundaries, and Int32 fields align on 4-byte boundaries.
  • Padding is added between fields to satisfy the alignment requirements.

For example, consider the following structure, which consists of two Byte fields and one Int32 field, when it's used with various values for the Pack field.

using System;

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

Important

To successfully compile the C# examples, you must specify the /unsafe compiler switch.

If you specify the default packing size, the size of the structure is 8 bytes. The two bytes occupy the first two bytes of memory, because bytes must align on one-byte boundaries. Because the default alignment of the type is 4 bytes, which is the size of its largest fields, i3, there are two bytes of padding followed by the integer field.

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

If Pack is set to 2, the size of the structure is 6 bytes. As before, the two bytes occupy the first two bytes of memory. Because fields now align on 2-byte boundaries, there's no padding between the second byte and the integer.

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

If Pack is set to 4, the size of the structure is the same as in the default case, where the type's alignment was defined by the size of its largest field, i3, which is 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

If Pack is set to 8, the size of the structure is the still same as in the default case, because the i3 field aligns on a 4-byte boundary, which is smaller than the 8-byte boundary specified by the Pack field.

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

To take another example, consider the following structure, which consists of two byte fields, one 32-bit signed integer field, one single-element byte array, and a decimal value. With the default packing size, the size of the structure is 28 bytes in .NET Framework and 32 bytes in .NET 5+. The two bytes occupy the first two bytes of memory, followed by two bytes of padding, followed by the integer. Next is the one-byte array, followed by three bytes of padding. Because a decimal value consists of several fields, the alignment is based on the largest of its fields rather than the size of the Decimal structure as a whole. In .NET 5 and later versions, the Decimal structure consists of two Int32 fields and one 8-byte field, so the Decimal field, d5, aligns on an 8-byte boundary. In .NET Framework, the Decimal structure consists of four Int32 fields, so the Decimal field, d5, aligns on an 4-byte boundary.

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

If Pack is set to 2, the size of the structure is 24 bytes. In comparison with the default alignment, the two bytes of padding between the two bytes and the integer have been removed because the type's alignment is now 4 rather than 2. And the three bytes of padding after a4 have been replaced by one byte of padding, since d5 now aligns on a 2-byte boundary rather than a 4-byte boundary.

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

If Pack is set to 16, the size of the structure is the same as in the default case, because all the alignment requirements in this structure are less than 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

The Pack field is frequently used when structures are exported during disk and network write operations. The field is also frequently used during platform invoke and interop operations.

Occasionally, the field is used to reduce memory requirements by producing a tighter packing size. However, this usage requires careful consideration of actual hardware constraints, and may actually degrade performance.

Applies to