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 = 320 }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:),其输出类型结构体为: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 ///5 /// 已过时为保持兼容性留下的结构体 6 /// 7 [Obsolete("已过时为保持兼容性留下的结构体")] 8 public OemId oemId; 9 ///10 /// 11 /// 12 public UInt32 dwPageSize;13 ///14 /// 应用程序或者DLL最小地址指针15 /// 16 public IntPtr lpMinimumApplicationAddress;17 ///18 /// 应用程序或者DLL最大地址指针19 /// 20 public IntPtr lpMaximumApplicationAddress;21 ///22 /// 系统内处理器配置的标记23 /// 24 public IntPtr dwActiveProcessorMask;25 ///26 /// 处理器数量27 /// 28 public UInt32 dwNumberOfProcessors;29 ///30 /// 处理器类型31 /// 32 [Obsolete("已过时,为兼容性保留,")]33 public UInt32 dwProcessorType;34 ///35 /// 虚拟内存分配的颗粒度36 /// 37 public UInt32 dwAllocationGranularity;38 ///39 /// 架构相关的处理器等级40 /// 41 public UInt16 wProcessorLevel;42 ///43 /// 处理器修订号44 /// 45 public UInt16 wProcessorRevision;46 }47 48 ///49 /// 已过时为保持兼容性留下的结构体50 /// 51 [StructLayout(LayoutKind.Explicit)]52 public struct OemId53 {54 [FieldOffset(0)]55 UInt32 dwOemId;56 ///57 /// 安装系统处理器架构58 /// 59 [FieldOffset(0)]60 UInt16 wProcessArchitecture;61 ///62 /// 将来预留字段63 /// 64 [FieldOffset(16)]65 UInt16 wReserved;66 }67 68 public enum ProcessArchitecture69 {70 ///71 /// x64 AMD or intel72 /// 73 PROCESSOR_ARCHITECTURE_AMD64 = 9,74 ///75 /// ARM76 /// 77 PROCESSOR_ARCHITECTURE_ARM = 5,78 ///79 /// ARM6480 /// 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.