StructLayout 的三种常见值怎么选
结构体默认是
LayoutKind.Auto,但运行时会自动重排字段顺序以优化内存对齐——这会导致无法与 C/C++ 互操作或序列化失败。实际开发中几乎总要显式指定:
LayoutKind.Sequential(按声明顺序布局,兼容性最好)或
LayoutKind.Explicit(手动控制每个字段偏移,用于位域、union 等场景)。除非你明确需要 .NET 自动优化且不对外暴露内存,否则别用
Auto。
Sequential 布局下字段顺序和对齐怎么生效
LayoutKind.Sequential保证字段按代码中声明顺序排列,但对齐仍受
Pack和字段类型影响。比如
int默认按 4 字节对齐,若前面是
byte,编译器会在中间插入 3 字节填充。可通过
[StructLayout(LayoutKind.Sequential, Pack = 1)]强制按 1 字节对齐,避免填充——这对网络协议解析或硬件寄存器映射很关键。但要注意:Pack=1 可能降低访问性能,尤其在 ARM 或某些 CPU 上。
Explicit 布局必须配 FieldOffset 才能编译
用
LayoutKind.Explicit时,每个字段都必须加
[FieldOffset(n)],否则编译报错
CS0629。它允许字段重叠(模拟 C union),例如同一块内存既当
int又当两个
short:
[StructLayout(LayoutKind.Explicit)]
public struct IntAsTwoShorts
{
[FieldOffset(0)] public int Value;
[FieldOffset(0)] public short Low;
[FieldOffset(2)] public short High;
}
注意:
FieldOffset值必须是非负整数,且不能超出结构体总大小;重叠字段的读写行为依赖 CPU 端序,跨平台需谨慎。
字符串、数组、引用类型在 StructLayout 中的陷阱
结构体里直接放
string或托管数组(如
int[])会导致
LayoutKind.Sequential失效,因为它们是引用类型,内存不在结构体内。正确做法是用
fixed数组或
MarshalAs: 固定长度字符数组:用
fixed char Name[32](需开启
unsafe) 字符串指针:用
IntPtr+
Marshal.PtrToStringAnsi非托管字符串:加
[MarshalAs(UnmanagedType.LPStr)] public string Name;,但只适用于 P/Invoke 场景
忘了这些限制,结构体传给 C DLL 时大概率崩溃或读到乱码。
