控制类或结构的数据字段在内存中的对齐方式
1 概述
由于内存的读取时间远远小于CPU的存储速度,这里用设定数据结构的对齐系数,即牺牲空间来换取时间的思想来提高CPU的存储效率。
2 C/C++ 语言写法
#pragma pack(push,4) /*指定按4字节对齐,等价于#pragma pack(push,4)*/
struct Test_Struct
{
char a1; //a1的自身对齐值为1, 小于对齐的四个字节,放在A空间
short a2; //a2的自身对齐值为2,1+2 小于对齐的四个字节,放在A空间
int a3; //a3的自身对齐值为4,4长度不足以容纳a1,a2,a3一起,所以a3额外进行4字节对齐
//这里一共使用8个字节长度
}
#pragma pack(pop) /*取消指定对齐,恢复缺省对齐,等价于#pragma pack(pop)*/
3 C# 语言写法
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct Test_Struct
{
byte a1;
byte a2;
int b1;
}
4 MS文档参考
5 C#内存对齐参考
5.1 StructLayoutAttribute.Pack 字段
命名空间:
System.Runtime.InteropServices
程序集:
netstandard.dll
字段值
Int32
控制类或结构的数据字段在内存中的对齐方式。
public int Pack;
5.2 注解
Pack字段控制类型字段在内存中的对齐方式。 它会影响 LayoutKind.Sequential 。 默认情况下,此值为0,指示当前平台的默认封装大小。 的值 Pack 必须为0、1、2、4、8、16、32、64或128:
使用以下规则对齐类型实例的字段:
-
该类型的对齐方式是它的最大元素的大小 (1、2、4、8等)、字节) 或指定的封装大小(以较小者为准)。
-
每个字段都必须与其自身大小的字段 (1、2、4、8等、字节) 或该类型的对齐方式相匹配,取两者中较小者。 因为该类型的默认对齐方式是其最大元素的大小,该大小大于或等于所有其他字段长度,这通常意味着字段按其大小对齐。 例如,即使类型中的最大字段为64位 (8 字节) 整数或 Pack 字段设置为8, Byte 字段将在1字节边界上对齐, Int16 字段在2字节边界对齐, Int32 字段在4字节边界上对齐。
-
在字段之间添加填充以满足对齐要求。
例如,Byte Int32 当与字段的各种值一起使用时,请考虑以下结构,其中包含两个字段和一个字段 Pack 。
C# 代码:
using System;
struct ExampleStruct
{
public byte b1;
public byte b2;
public int i3;
}
重要
若要成功编译 c # 示例,必须指定 /unsafe 编译器开关。
6 C# 内存对齐 - 示例1
内存对齐 - 示例1 - pack=0
如果指定默认的打包大小,则结构的大小为8个字节。 这两个字节占用前两个字节的内存,因为字节必须在一个字节边界上对齐。 由于该类型的默认对齐方式为4字节(这是其最大字段的大小),因此 i3 有两个填充字节,后跟整数字段。
C#
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, Pack=0)]
struct ExampleStruct
{
public byte b1;
public byte b2;
public int i3;
}
public class Example
{
public unsafe static void Main()
{
ExampleStruct ex = new ExampleStruct();
byte* addr = (byte*) &ex;
Console.WriteLine("Size: {0}", sizeof(ExampleStruct));
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
内存对齐 - 示例1 - pack=2
如果 Pack 设置为2,则结构的大小为6个字节。 与之前一样,两个字节占用前两个字节的内存。 由于字段现在按2字节边界对齐,因此,在第二个字节和整数之间没有任何空白。
C#
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, Pack=2)]
struct ExampleStruct
{
public byte b1;
public byte b2;
public int i3;
}
public class Example
{
public unsafe static void Main()
{
ExampleStruct ex = new ExampleStruct();
byte* addr = (byte*) &ex;
Console.WriteLine("Size: {0}", sizeof(ExampleStruct));
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
内存对齐 - 示例1 - pack=4
如果 Pack 设置为4,则结构的大小与默认大小写相同,其中,类型的对齐方式由其最大字段的大小定义,即 i3 4。
C#
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, Pack=4)]
struct ExampleStruct
{
public byte b1;
public byte b2;
public int i3;
}
public class Example
{
public unsafe static void Main()
{
ExampleStruct ex = new ExampleStruct();
byte* addr = (byte*) &ex;
Console.WriteLine("Size: {0}", sizeof(ExampleStruct));
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
内存对齐 - 示例1 - pack=8
如果 Pack 设置为8,则结构的大小与默认情况下的大小相同,因为 i3 字段在4字节边界上对齐,这小于 Pack 字段指定的8字节边界。
C#
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, Pack=8)]
struct ExampleStruct
{
public byte b1;
public byte b2;
public int i3;
}
public class Example
{
public unsafe static void Main()
{
ExampleStruct ex = new ExampleStruct();
byte* addr = (byte*) &ex;
Console.WriteLine("Size: {0}", sizeof(ExampleStruct));
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
7 C# 内存对齐 - 示例2
内存对齐 - 示例2
若要获得另一个示例,请考虑以下结构,其中包含两个字节字段、1 32 位带符号整数字段、单元素字节数组和十进制值。 对于默认的封装大小,结构的大小为28个字节。 这两个字节占用前两个字节的内存,后跟两个填充字节,后跟整数。 接下来是单字节数组,后跟三个填充字节。 最后, Decimal 由于一个十进制值由四个字段组成,因此该字段 d5 在4字节边界上对齐, Int32 因此,其对齐方式基于其字段的最大大小,而不是 Decimal 整个结构的大小。
C#
using System;
using System.Runtime.InteropServices;
unsafe struct ExampleStruct2
{
public byte b1;
public byte b2;
public int i3;
public fixed byte a4[1];
public decimal d5;
}
public class Example
{
public unsafe static void Main()
{
ExampleStruct2 ex = new ExampleStruct2();
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);
Console.WriteLine("a4 Offset: {0}", ex.a4 - addr);
Console.WriteLine("d5 Offset: {0}", (byte*) &ex.d5 - addr);
}
}
// The example displays the following output:
// Size: 28
// b1 Offset: 0
// b2 Offset: 1
// i3 Offset: 4
// a4 Offset: 8
// d5 Offset: 12
内存对齐 - 示例2 - pack=2
如果 Pack 设置为2,则结构的大小为24个字节。 与默认对齐方式相比,两个字节和整数之间的两个填充字节已被删除,因为该类型的对齐方式现在为4而不是2。 并将填充后的三个字节 a4 替换为一个填充字节,因为 d5 现在在2字节边界上对齐,而不是在4字节边界上对齐。
C#
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, Pack = 2)]
unsafe struct ExampleStruct2
{
public byte b1;
public byte b2;
public int i3;
public fixed byte a4[1];
public decimal d5;
}
public class Example
{
public unsafe static void Main()
{
ExampleStruct2 ex = new ExampleStruct2();
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);
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
内存对齐 - 示例2 - pack=8
如果 Pack 设置为8,则结构的大小与默认情况相同,因为此结构中的所有对齐要求都小于8。
C#
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, Pack = 8)]
unsafe struct ExampleStruct2
{
public byte b1;
public byte b2;
public int i3;
public fixed byte a4[1];
public decimal d5;
}
public class Example
{
public unsafe static void Main()
{
ExampleStruct2 ex = new ExampleStruct2();
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);
Console.WriteLine("a4 Offset: {0}", ex.a4 - addr);
Console.WriteLine("d5 Offset: {0}", (byte*) &ex.d5 - addr);
}
}
// The example displays the following output:
// Size: 28
// b1 Offset: 0
// b2 Offset: 1
// i3 Offset: 4
// a4 Offset: 8
// d5 Offset: 12
内存对齐 - 说明
在 Pack 磁盘和网络写入操作过程中导出结构时,通常会使用该字段。 在平台调用和互操作操作期间,也经常使用此字段。
偶尔,该字段用于通过生成更严格的封装大小来减少内存需求。 但是,这种用法需要仔细考虑实际的硬件限制,并可能会降低性能。
C#内存对齐适用版本
产品 版本
.NET | Core 1.0, Core 1.1, Core 2.0, Core 2.1, Core 2.2, Core 3.0, Core 3.1, 5, 6, 7 |
---|---|
.NET Framework | 1.1, 2.0, 3.0, 3.5, 4.0, 4.5, 4.5.1, 4.5.2, 4.6, 4.6.1, 4.6.2, 4.7, 4.7.1, 4.7.2, 4.8 |
.NET Standard | 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 2.0, 2.1 |
UWP | 10.0 |
Xamarin.iOS | 10.8 |
Xamarin.Mac | 3.0 |