CLR如何控制类型中的字段布局
为提高性能,在未指定类型或者结构体的字段的任何排列方式时,CLR会按照自己认为最合适的方式进行重新排列,即内存对齐。
如何指定类或者结构体内字段的排列方式
在类型或者结构体内添加特性System.Runtime.InteropServices.StructLayoutAttribute;
传递枚举参数LayoutKind
具体参数说明(微软代码有介绍):
1 // 2 // 摘要: 3 // 当将对象导出到非托管代码时,控制该对象的布局。 4 [ComVisible(true)] 5 public enum LayoutKind 6 { 7 // 8 // 摘要: 9 // 对象的成员顺序依次布局,它们出现导出到非托管内存时的顺序。 成员进行布局中指定的封装根据System.Runtime.InteropServices.StructLayoutAttribute.Pack,和可以是不连续。 10 Sequential = 0, 11 // 12 // 摘要: 13 // 非托管内存中的每个成员的对象的精确位置显式控制,受约束的设置System.Runtime.InteropServices.StructLayoutAttribute.Pack字段。 14 // 每个成员必须使用System.Runtime.InteropServices.FieldOffsetAttribute指示该字段的类型中的位置。 15 Explicit = 2, 16 // 17 // 摘要: 18 // 非托管内存中某个对象的成员,则运行时会自动选择适当的布局。 无法在托管代码之外公开使用此枚举成员定义的对象。 尝试这样做将引发异常。 19 Auto = 3 20 } 21 }
需要注意的是Microsof C#编译器总是默认的为引用类型选择Layoutkind.Auto,为值类型选择LayoutKind.Sequential(也许其他的C#编译器不是这么做的)。
LayoutKind.Sequential一般用于和非托管代码进行交互,若不与非托管代码进行交互可以显式指定为Auto可以提高性能。
LayoutKind.Explicit:精确指定字段偏移量(相对于实例的起始位置),使用FieldOffSet特性指定。
Tips:
对于LayoutKind.Explicit:
1.一般用于C/C++具有union的类,该类的数据成员在内存中的存储互相重叠,每个数据成员都冲相同的内存地址开始,分配给union的存储区数量是它包含最大数据成员所需的内存数,同一时刻只有一个成员可以被赋值。
2.引用类型和值类型互相重叠是不合法的,多个引用类型互相重叠时,该类型无法验证,多个值类型互相重叠是合法的,但所有重叠字节都能通过公共字段访问,方能够验证;
实际应用
通过win32函数获取当前操作系统的逻辑CPU核心数:GetSystemInfo或者GetNativeSystemInfo(微软API:https://msdn.microsoft.com/en-us/library/ms724381(v=VS.85).aspx),其输出类型结构体为:SYSTEM_INFO:
该结构体定义:
typedef struct _SYSTEM_INFO { union { DWORD dwOemId; struct { WORD wProcessorArchitecture; WORD wReserved; }; }; DWORD dwPageSize; LPVOID lpMinimumApplicationAddress; LPVOID lpMaximumApplicationAddress; DWORD_PTR dwActiveProcessorMask; DWORD dwNumberOfProcessors; DWORD dwProcessorType; DWORD dwAllocationGranularity; WORD wProcessorLevel; WORD wProcessorRevision; } SYSTEM_INFO;
其中含有一个匿名联合体体,该联体内字段dwOemid与匿名联合体中的匿名结构体起始地址一样,由于dwOemid是DWORD类,而匿名结构体内的两个字段都为WORD类型,DWORD类型是4个字节类型,WORD是双字节,所以dwOemId的低16位与wProcessArchitecture内存共用,dwOemId的高16位与wReserved内存共用;所以在C#代码中可以这样声明SYSTEM_INFO结构体:
1 [StructLayout(LayoutKind.Sequential)] 2 public struct System_Info 3 { 4 /// <summary> 5 /// 已过时为保持兼容性留下的结构体 6 /// </summary> 7 [Obsolete("已过时为保持兼容性留下的结构体")] 8 public OemId oemId; 9 /// <summary> 10 /// 11 /// </summary> 12 public UInt32 dwPageSize; 13 /// <summary> 14 /// 应用程序或者DLL最小地址指针 15 /// </summary> 16 public IntPtr lpMinimumApplicationAddress; 17 /// <summary> 18 /// 应用程序或者DLL最大地址指针 19 /// </summary> 20 public IntPtr lpMaximumApplicationAddress; 21 /// <summary> 22 /// 系统内处理器配置的标记 23 /// </summary> 24 public IntPtr dwActiveProcessorMask; 25 /// <summary> 26 /// 处理器数量 27 /// </summary> 28 public UInt32 dwNumberOfProcessors; 29 /// <summary> 30 /// 处理器类型 31 /// </summary> 32 [Obsolete("已过时,为兼容性保留,")] 33 public UInt32 dwProcessorType; 34 /// <summary> 35 /// 虚拟内存分配的颗粒度 36 /// </summary> 37 public UInt32 dwAllocationGranularity; 38 /// <summary> 39 /// 架构相关的处理器等级 40 /// </summary> 41 public UInt16 wProcessorLevel; 42 /// <summary> 43 /// 处理器修订号 44 /// </summary> 45 public UInt16 wProcessorRevision; 46 } 47 48 /// <summary> 49 /// 已过时为保持兼容性留下的结构体 50 /// </summary> 51 [StructLayout(LayoutKind.Explicit)] 52 public struct OemId 53 { 54 [FieldOffset(0)] 55 UInt32 dwOemId; 56 /// <summary> 57 /// 安装系统处理器架构 58 /// </summary> 59 [FieldOffset(0)] 60 UInt16 wProcessArchitecture; 61 /// <summary> 62 /// 将来预留字段 63 /// </summary> 64 [FieldOffset(16)] 65 UInt16 wReserved; 66 } 67 68 public enum ProcessArchitecture 69 { 70 /// <summary> 71 /// x64 AMD or intel 72 /// </summary> 73 PROCESSOR_ARCHITECTURE_AMD64 = 9, 74 /// <summary> 75 /// ARM 76 /// </summary> 77 PROCESSOR_ARCHITECTURE_ARM = 5, 78 /// <summary> 79 /// ARM64 80 /// </summary> 81 PROCESSOR_ARCHITECTURE_ARM64 = 12, 82 PROCESSOR_ARCHITECTURE_IA64 = 6, 83 PROCESSOR_ARCHITECTURE_INTEL = 0, 84 PROCESSOR_ARCHITECTURE_UNKNOWN = 0xffff, 85 }
然后调用下看看结果。
问题
1.LayoutKind.Auto为什么可以提高性能?
https://www.cnblogs.com/kex1n/archive/2009/06/16/2286527.html
2.什么是类型验证?如何进行类型验证?
参考:
1.CLR Via C#;2.